From 3c81ece847891b6d948ecb1ba40b3520cd4dc1e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 12 Apr 2026 22:56:55 +0100 Subject: [PATCH 01/25] started on creating a postgres connection --- CMakeLists.txt | 8 +++- examples/CMakeLists.txt | 5 +++ examples/postgres.cpp | 91 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 examples/postgres.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fa12d47..d85c0c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,8 +18,9 @@ option( option( BEMAN_NET_BUILD_EXAMPLES "Enable building examples. Default: ON. Values: { ON, OFF }." - ${PROJECT_IS_TOP_LEVEL} + TRUE ) +# ${PROJECT_IS_TOP_LEVEL} if(LINUX) option(BEMAN_NET_WITH_URING "Enable liburing io context" OFF) @@ -52,6 +53,9 @@ if(BEMAN_NET_BUILD_TESTS) add_subdirectory(tests/beman/net) endif() -if(BEMAN_NET_BUILD_EXAMPLES) +message(NOTICE "Message test") +set(BEMAN_NET_BUILD_EXAMPLES TRUE) +if(${BEMAN_NET_BUILD_EXAMPLES}) + message(NOTICE "Building examples") add_subdirectory(examples) endif() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 3961058..6033e01 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -29,11 +29,13 @@ set(EXAMPLES http-server milano http-munich + postgres server taps task ) set(xEXAMPLES taps) +set(EXAMPLES postgres) foreach(EXAMPLE ${EXAMPLES}) set(EXAMPLE_TARGET beman.net.examples.${EXAMPLE}) @@ -42,6 +44,9 @@ foreach(EXAMPLE ${EXAMPLES}) # XXX target_compile_definitions( ${EXAMPLE_TARGET} PRIVATE BEMAN_NET_USE_URING) endif() target_sources(${EXAMPLE_TARGET} PRIVATE ${EXAMPLE}.cpp) + target_include_directories(${EXAMPLE_TARGET} PRIVATE /Library/PostgreSQL/18/include) + target_link_directories(${EXAMPLE_TARGET} PRIVATE /Library/PostgreSQL/18/lib) + target_link_libraries(${EXAMPLE_TARGET} PRIVATE pq) target_link_libraries(${EXAMPLE_TARGET} PRIVATE beman::net_headers) target_link_libraries(${EXAMPLE_TARGET} PRIVATE beman::task_headers) endforeach() diff --git a/examples/postgres.cpp b/examples/postgres.cpp new file mode 100644 index 0000000..35c6e06 --- /dev/null +++ b/examples/postgres.cpp @@ -0,0 +1,91 @@ +// examples/postgres.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include + +namespace ex = beman::execution; +namespace net = beman::net; + +namespace pq { + using connection = std::unique_ptr; + using result = std::unique_ptr; + + struct error { + std::string msg; + error(const connection& conn) : msg(PQerrorMessage(conn.get())) {} + const char* what() const noexcept { return msg.c_str(); }; + friend std::ostream& operator<< (std::ostream& os, const error& err) { + return os << err.msg; + } + }; + + // PQsetnonblocking(const PGconn *conn, int arg) - set non-blocking mode to avoid write blocks + // PQsocket(const PGconn *conn) - get socket + // PQflush(const PGconn *conn) - flush output buffer, return 1 if still pending data + // PQisBusy(const PGconn *conn) - PQgetResult() would block + // PQconsumeInput(const PGconn *conn) - consume available input, clear socket stat + // PQgetResult(const PGconn *conn) - get result, return nullptr if no more results, or would block + + struct exec { + using sender_concept = ex::sender_t; + using completion_signatures = ex::completion_signatures; + template + static consteval completion_signatures get_completion_signatures() noexcept { return {}; } + + template + struct state { + using operation_state_concept = ex::operation_state_t; + std::remove_cvref_t receiver; + connection& conn; + std::string query; + auto start() noexcept -> void { + } + }; + + template + auto connect(Receiver&& receiver) { + return state{std::forward(receiver), conn, std::move(query)}; + } + + exec(connection& conn, std::string query) : conn(conn), query(std::move(query)) {} + connection& conn; + std::string query; + }; + + inline constexpr double sleep_time = 3.0; +} + +int main() { + std::cout << std::unitbuf; + net::io_context io; + pq::connection conn(PQconnectdb("user=sruser dbname=srchat")); + PQsetnonblocking(conn.get(), 1); + + if (PQstatus(conn.get()) != CONNECTION_OK) { + std::cout << "Connection to database failed: " << pq::error(conn) << '\n'; ; + return 1; + } + const char*const query_version = "SELECT version(), pg_sleep(0)"; + pq::result res(PQexec(conn.get(), query_version)); + if (PQresultStatus(res.get()) != PGRES_TUPLES_OK) { + std::cout << "SELECT failed: " << pq::error(conn) << '\n'; + return 1; + } + std::cout << "PostgreSQL version: " << PQgetvalue(res.get(), 0, 0) << '\n'; + + [[maybe_unused]] auto result = ex::sync_wait(pq::exec(conn, query_version)); + if (result) { + auto[res] = *std::move(result); + if (PQresultStatus(res.get()) != PGRES_TUPLES_OK) { + std::cout << "SELECT failed: " << pq::error(conn) << '\n'; + return 1; + } + std::cout << "PostgreSQL version: " << PQgetvalue(res.get(), 0, 0) << '\n'; + } +} From 2030284ca31569bac845f9763a3848c9e1a2aa19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Fri, 17 Apr 2026 00:36:11 +0100 Subject: [PATCH 02/25] saving current intermediate state --- examples/CMakeLists.txt | 2 +- examples/empty.cpp | 3 ++ examples/populate-postgres.cpp | 55 +++++++++++++++++++++++++++++ examples/postgres.cpp | 63 ++++++++++++++++++++++++++++------ 4 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 examples/populate-postgres.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 6033e01..25fdd1b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -35,7 +35,7 @@ set(EXAMPLES task ) set(xEXAMPLES taps) -set(EXAMPLES postgres) +set(EXAMPLES postgres populate-postgres) foreach(EXAMPLE ${EXAMPLES}) set(EXAMPLE_TARGET beman.net.examples.${EXAMPLE}) diff --git a/examples/empty.cpp b/examples/empty.cpp index 862272a..48ecab4 100644 --- a/examples/empty.cpp +++ b/examples/empty.cpp @@ -28,6 +28,8 @@ std::unordered_map files{ }; auto main() -> int { + std::cout << std::unitbuf << "hello world\n"; +#if 0 net::io_context context; net::ip::tcp::endpoint ep(net::ip::address_v4::any(), 12345); net::ip::tcp::acceptor server(context, ep); @@ -35,4 +37,5 @@ auto main() -> int { ex::sync_wait(net::async_accept(server)); context.run(); +#endif } diff --git a/examples/populate-postgres.cpp b/examples/populate-postgres.cpp new file mode 100644 index 0000000..606aff1 --- /dev/null +++ b/examples/populate-postgres.cpp @@ -0,0 +1,55 @@ +// examples/postgres.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +namespace pq { + using connection = std::unique_ptr; + using result = std::unique_ptr; + + struct error { + std::string msg; + error(const connection& conn) : msg(PQerrorMessage(conn.get())) {} + const char* what() const noexcept { return msg.c_str(); }; + friend std::ostream& operator<< (std::ostream& os, const error& err) { + return os << err.msg; + } + }; +} + +int main() { + std::cout << std::unitbuf; + pq::connection conn(PQconnectdb("user=sruser dbname=sruser")); + + if (PQstatus(conn.get()) != CONNECTION_OK) { + std::cout << "Connection to database failed: " << pq::error(conn) << '\n'; ; + return 1; + } + const char*const query_version = "SELECT version(), pg_sleep(0)"; + pq::result res(PQexec(conn.get(), query_version)); + if (PQresultStatus(res.get()) != PGRES_TUPLES_OK) { + std::cout << "SELECT failed: " << pq::error(conn) << '\n'; + return 1; + } + std::cout << "PostgreSQL version: " << PQgetvalue(res.get(), 0, 0) << '\n'; + + std::string input("/usr/share/dict/words"); + std::cout << "populating from '" << input << "'\n"; + std::ifstream file(input); + std::string message; + for (std::size_t i{}; i < 100 && std::getline(file, message); ++i) { + std::ostringstream ins; + ins << "insert into messages (key, message) values(" << i << ", '" << message << "');"; + std::cout << "inserting: " << ins.str() << '\n'; + pq::result res(PQexec(conn.get(), ins.str().c_str())); + if (PQresultStatus(res.get()) != PGRES_COMMAND_OK) { + std::cout << "INSERT failed: " << pq::error(conn) << '\n'; + return 1; + } + } +} diff --git a/examples/postgres.cpp b/examples/postgres.cpp index 35c6e06..4ffedd7 100644 --- a/examples/postgres.cpp +++ b/examples/postgres.cpp @@ -4,8 +4,9 @@ #include #include #include -#include +#include #include +#include #include #include @@ -18,6 +19,7 @@ namespace pq { struct error { std::string msg; + explicit error(const char* m) : msg(m) {} error(const connection& conn) : msg(PQerrorMessage(conn.get())) {} const char* what() const noexcept { return msg.c_str(); }; friend std::ostream& operator<< (std::ostream& os, const error& err) { @@ -31,6 +33,8 @@ namespace pq { // PQisBusy(const PGconn *conn) - PQgetResult() would block // PQconsumeInput(const PGconn *conn) - consume available input, clear socket stat // PQgetResult(const PGconn *conn) - get result, return nullptr if no more results, or would block + // PQsetSingleRowMode(PGconn *conn) - set single row mode, return 0 on failure + // PQsetChunkedMode(PGconn *conn, int arg) - set chunked mode, return 0 on failure struct exec { using sender_concept = ex::sender_t; @@ -45,6 +49,27 @@ namespace pq { connection& conn; std::string query; auto start() noexcept -> void { + std::cout << "exec.start()\n"; + if (!PQsendQuery(conn.get(), query.c_str())) { + std::cout << "PQsendQuery failed: " << PQerrorMessage(conn.get()) << "\n"; + ex::set_error(std::move(receiver), pq::error(PQerrorMessage(conn.get()))); + return; + } + + if (false && PQisBusy(conn.get())) { + ex::set_error(std::move(receiver), pq::error("PQsendQuery would block")); + } + else { + complete(); + } + } + void complete() noexcept { + if (pq::result res{pq::result(PQgetResult(conn.get()))}) { + ex::set_value(std::move(receiver), std::move(res)); + } + else { + ex::set_error(std::move(receiver), pq::error(conn)); + } } }; @@ -64,28 +89,46 @@ namespace pq { int main() { std::cout << std::unitbuf; net::io_context io; - pq::connection conn(PQconnectdb("user=sruser dbname=srchat")); + std::cout << "connecting\n"; + pq::connection conn(PQconnectdb("user=sruser dbname=sruser")); PQsetnonblocking(conn.get(), 1); + std::cout << "connection created\n"; if (PQstatus(conn.get()) != CONNECTION_OK) { std::cout << "Connection to database failed: " << pq::error(conn) << '\n'; ; return 1; } - const char*const query_version = "SELECT version(), pg_sleep(0)"; + [[maybe_unused]] const char*const query_version = "SELECT version(), pg_sleep(3)"; +#if 0 pq::result res(PQexec(conn.get(), query_version)); if (PQresultStatus(res.get()) != PGRES_TUPLES_OK) { std::cout << "SELECT failed: " << pq::error(conn) << '\n'; return 1; } std::cout << "PostgreSQL version: " << PQgetvalue(res.get(), 0, 0) << '\n'; +#endif - [[maybe_unused]] auto result = ex::sync_wait(pq::exec(conn, query_version)); - if (result) { - auto[res] = *std::move(result); - if (PQresultStatus(res.get()) != PGRES_TUPLES_OK) { - std::cout << "SELECT failed: " << pq::error(conn) << '\n'; - return 1; + std::string query( + "select *, pg_sleep(0.1) from messages where 0 <= key and key < 3;" + "select *, pg_sleep(0.1) from messages where 3 <= key and key < 6;" + ); + try { + [[maybe_unused]] auto result = ex::sync_wait(pq::exec(conn, query)); + if (result) { + auto[res] = *std::move(result); + if (PQresultStatus(res.get()) != PGRES_TUPLES_OK) { + std::cout << "SELECT failed: " << pq::error(conn) << '\n'; + return 1; + } + for (int i{}, n{PQntuples(res.get())}; i < n; ++i) { + for (int j{}, m{PQnfields(res.get())}; j < m; ++j) { + std::cout << PQgetvalue(res.get(), i, j) << ','; + } + std::cout << '\n'; + } } - std::cout << "PostgreSQL version: " << PQgetvalue(res.get(), 0, 0) << '\n'; + } + catch (const pq::error& e) { + std::cout << "Error: " << e << '\n'; } } From d22b02898ec83bcaefbe1d7d295eb2f55cf268d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sat, 18 Apr 2026 22:32:25 +0100 Subject: [PATCH 03/25] made some progress on the poll() implementation --- examples/postgres.cpp | 77 +++++++++++++++-------- include/beman/net/detail/basic_socket.hpp | 2 +- include/beman/net/detail/context_base.hpp | 2 + include/beman/net/detail/event_type.hpp | 15 +++++ include/beman/net/detail/io_context.hpp | 3 + include/beman/net/detail/operations.hpp | 30 +++++++++ include/beman/net/detail/poll_context.hpp | 10 +++ 7 files changed, 111 insertions(+), 28 deletions(-) diff --git a/examples/postgres.cpp b/examples/postgres.cpp index 4ffedd7..1176a69 100644 --- a/examples/postgres.cpp +++ b/examples/postgres.cpp @@ -14,13 +14,23 @@ namespace ex = beman::execution; namespace net = beman::net; namespace pq { - using connection = std::unique_ptr; + struct connection { + using handle_t = std::unique_ptr; + + handle_t handle; + net::ip::tcp::socket socket; + connection(net::io_context& io, PGconn* conn): handle(conn), socket(io.get_scheduler().get_context(), io.make_socket(PQsocket(handle.get()))) {} + + net::ip::tcp::socket& get_socket() { return this->socket; } + operator PGconn*() { return this->handle.get(); } + operator const PGconn*() const { return this->handle.get(); } + }; using result = std::unique_ptr; struct error { std::string msg; explicit error(const char* m) : msg(m) {} - error(const connection& conn) : msg(PQerrorMessage(conn.get())) {} + error(const connection& conn) : msg(PQerrorMessage(conn)) {} const char* what() const noexcept { return msg.c_str(); }; friend std::ostream& operator<< (std::ostream& os, const error& err) { return os << err.msg; @@ -45,31 +55,44 @@ namespace pq { template struct state { using operation_state_concept = ex::operation_state_t; - std::remove_cvref_t receiver; - connection& conn; - std::string query; - auto start() noexcept -> void { - std::cout << "exec.start()\n"; - if (!PQsendQuery(conn.get(), query.c_str())) { - std::cout << "PQsendQuery failed: " << PQerrorMessage(conn.get()) << "\n"; - ex::set_error(std::move(receiver), pq::error(PQerrorMessage(conn.get()))); - return; - } - if (false && PQisBusy(conn.get())) { - ex::set_error(std::move(receiver), pq::error("PQsendQuery would block")); + struct env { + using error_types = ex::completion_signatures; + }; + static ex::task work(connection& conn, std::string query) noexcept { + std::cout << "exec.work()\n"; + if (!PQsendQuery(conn, query.c_str())) { + std::cout << "PQsendQuery failed: " << PQerrorMessage(conn) << "\n"; + co_yield ex::with_error(pq::error(conn)); } - else { - complete(); + PQflush(conn); + while (PQisBusy(conn)) { + std::cout << "co_awaiting poll\n"; + auto evs = co_await net::async_poll(conn.get_socket(), net::event_type::in); + std::cout << "co_awaiting done=" << evs << "\n"; + if (!PQconsumeInput(conn)) { + std::cout << "PQconsumeInput failed: " << PQerrorMessage(conn) << "\n"; + co_yield ex::with_error(pq::error(conn)); + } + break; } - } - void complete() noexcept { - if (pq::result res{pq::result(PQgetResult(conn.get()))}) { - ex::set_value(std::move(receiver), std::move(res)); + if (pq::result res{pq::result(PQgetResult(conn))}) { + co_return std::move(res); } else { - ex::set_error(std::move(receiver), pq::error(conn)); + co_yield ex::with_error(pq::error(conn)); } + std::unreachable(); + } + using inner_state_t = ex::connect_result_t(), std::string{})), Receiver&&>; + + inner_state_t inner_state; + + state(Receiver&& r, connection& conn, std::string query) + : inner_state(ex::connect(work(conn, std::move(query)), std::forward(r))) {} + + auto start() noexcept -> void { + ex::start(this->inner_state); } }; @@ -90,17 +113,17 @@ int main() { std::cout << std::unitbuf; net::io_context io; std::cout << "connecting\n"; - pq::connection conn(PQconnectdb("user=sruser dbname=sruser")); - PQsetnonblocking(conn.get(), 1); + pq::connection conn(io, PQconnectdb("user=sruser dbname=sruser")); + PQsetnonblocking(conn, 1); std::cout << "connection created\n"; - if (PQstatus(conn.get()) != CONNECTION_OK) { + if (PQstatus(conn) != CONNECTION_OK) { std::cout << "Connection to database failed: " << pq::error(conn) << '\n'; ; return 1; } [[maybe_unused]] const char*const query_version = "SELECT version(), pg_sleep(3)"; #if 0 - pq::result res(PQexec(conn.get(), query_version)); + pq::result res(PQexec(conn, query_version)); if (PQresultStatus(res.get()) != PGRES_TUPLES_OK) { std::cout << "SELECT failed: " << pq::error(conn) << '\n'; return 1; @@ -109,8 +132,8 @@ int main() { #endif std::string query( - "select *, pg_sleep(0.1) from messages where 0 <= key and key < 3;" - "select *, pg_sleep(0.1) from messages where 3 <= key and key < 6;" + "select *, pg_sleep(10.1) from messages where 0 <= key and key < 3;" + //"select *, pg_sleep(0.1) from messages where 3 <= key and key < 6;" ); try { [[maybe_unused]] auto result = ex::sync_wait(pq::exec(conn, query)); diff --git a/include/beman/net/detail/basic_socket.hpp b/include/beman/net/detail/basic_socket.hpp index 7937cb7..b324c33 100644 --- a/include/beman/net/detail/basic_socket.hpp +++ b/include/beman/net/detail/basic_socket.hpp @@ -21,7 +21,7 @@ class beman::net::basic_socket : public ::beman::net::socket_base { private: ::beman::net::detail::context_base* d_context; - protocol_type d_protocol{::beman::net::ip::tcp::v6()}; + protocol_type d_protocol{::beman::net::ip::tcp::v6()}; //-dk:TODO should initialize based on protocol_type ::beman::net::detail::socket_id d_id{::beman::net::detail::socket_id::invalid}; public: diff --git a/include/beman/net/detail/context_base.hpp b/include/beman/net/detail/context_base.hpp index 0132869..4cfc44d 100644 --- a/include/beman/net/detail/context_base.hpp +++ b/include/beman/net/detail/context_base.hpp @@ -26,6 +26,7 @@ struct beman::net::detail::context_base { virtual auto complete() -> void = 0; }; + using poll_operation = ::beman::net::detail::io_operation<::std::tuple>; using accept_operation = ::beman::net::detail::io_operation< ::std::tuple<::beman::net::detail::endpoint, ::socklen_t, ::std::optional<::beman::net::detail::socket_id>>>; using connect_operation = ::beman::net::detail::io_operation<::std::tuple<::beman::net::detail::endpoint>>; @@ -51,6 +52,7 @@ struct beman::net::detail::context_base { virtual auto cancel(::beman::net::detail::io_base*, ::beman::net::detail::io_base*) -> void = 0; virtual auto schedule(::beman::net::detail::context_base::task*) -> void = 0; + virtual auto poll(::beman::net::detail::context_base::poll_operation*) -> ::beman::net::detail::submit_result { return {}; }; //-dk:TODO = 0; virtual auto accept(::beman::net::detail::context_base::accept_operation*) -> ::beman::net::detail::submit_result = 0; virtual auto connect(::beman::net::detail::context_base::connect_operation*) diff --git a/include/beman/net/detail/event_type.hpp b/include/beman/net/detail/event_type.hpp index 6d44ea2..ac5ed18 100644 --- a/include/beman/net/detail/event_type.hpp +++ b/include/beman/net/detail/event_type.hpp @@ -5,6 +5,7 @@ #define INCLUDED_INCLUDE_BEMAN_NET_DETAIL_EVENT_TYPE #include +#include // ---------------------------------------------------------------------------- @@ -13,6 +14,20 @@ enum class event_type { none = 0x00, in = 0x01, out = 0x02, in_out = 0x03 }; constexpr ::beman::net::event_type operator&(::beman::net::event_type e0, ::beman::net::event_type e1) { return ::beman::net::event_type(::std::uint8_t(e0) & ::std::uint8_t(e1)); } +inline std::ostream& operator<<(std::ostream& os, ::beman::net::event_type e) { + switch (e) + {default: + return os << "invalid(" << ::std::uint8_t(e) << ")"; + case ::beman::net::event_type::none: + return os << "none"; + case ::beman::net::event_type::in: + return os << "in"; + case ::beman::net::event_type::out: + return os << "out"; + case ::beman::net::event_type::in_out: + return os << "in|out"; + } +} } // namespace beman::net // ---------------------------------------------------------------------------- diff --git a/include/beman/net/detail/io_context.hpp b/include/beman/net/detail/io_context.hpp index 4409f73..35cb836 100644 --- a/include/beman/net/detail/io_context.hpp +++ b/include/beman/net/detail/io_context.hpp @@ -68,6 +68,9 @@ class beman::net::io_context { io_context(::beman::net::detail::context_base& context) : d_owned(), d_context(context) {} io_context(io_context&&) = delete; + auto make_socket(::beman::net::detail::native_handle_type fd) -> ::beman::net::detail::socket_id { + return this->d_context.make_socket(fd); + } auto make_socket(int d, int t, int p, ::std::error_code& error) -> ::beman::net::detail::socket_id { return this->d_context.make_socket(d, t, p, error); } diff --git a/include/beman/net/detail/operations.hpp b/include/beman/net/detail/operations.hpp index a6ac80b..7aedb75 100644 --- a/include/beman/net/detail/operations.hpp +++ b/include/beman/net/detail/operations.hpp @@ -13,6 +13,7 @@ // ---------------------------------------------------------------------------- namespace beman::net::detail { +struct poll_desc; struct accept_desc; struct connect_desc; struct send_desc; @@ -22,6 +23,9 @@ struct receive_from_desc; } // namespace beman::net::detail namespace beman::net { +using async_poll_t = ::beman::net::detail::sender_cpo<::beman::net::detail::poll_desc>; +inline constexpr async_poll_t async_poll{}; + using async_accept_t = ::beman::net::detail::sender_cpo<::beman::net::detail::accept_desc>; inline constexpr async_accept_t async_accept{}; @@ -42,6 +46,32 @@ inline constexpr async_receive_from_t async_receive_from{}; // ---------------------------------------------------------------------------- +struct beman::net::detail::poll_desc { + using operation = ::beman::net::detail::context_base::poll_operation; + template + struct data { + using completion_signature = ::beman::net::detail::ex::set_value_t(::beman::net::event_type); + + Socket& d_socket; + ::beman::net::event_type d_mask; + data(Socket& socket, ::beman::net::event_type mask): d_socket(socket), d_mask(mask) {} + + auto id() const { return this->d_socket.id(); } + auto events() const -> ::beman::net::event_type { return this->d_mask; } + auto set_value([[maybe_unused]] operation& o, auto&& receiver) { + ::beman::net::detail::ex::set_value( + ::std::move(receiver), + //::std::get<0>(o) + ::beman::net::event_type{} + ); + } + auto get_scheduler() { return this->d_socket.get_scheduler(); } + auto submit([[maybe_unused]] auto* base) -> ::beman::net::detail::submit_result { + return ::beman::net::detail::submit_result::ready; + } + }; +}; + struct beman::net::detail::accept_desc { using operation = ::beman::net::detail::context_base::accept_operation; template diff --git a/include/beman/net/detail/poll_context.hpp b/include/beman/net/detail/poll_context.hpp index 6f63e19..ca88d0d 100644 --- a/include/beman/net/detail/poll_context.hpp +++ b/include/beman/net/detail/poll_context.hpp @@ -200,6 +200,16 @@ struct beman::net::detail::poll_context final : ::beman::net::detail::context_ba tsk->next = this->d_tasks; this->d_tasks = tsk; } + auto poll(::beman::net::detail::context_base::poll_operation* op) + -> ::beman::net::detail::submit_result override final { + op->context = this; + op->work = [](::beman::net::detail::context_base&, ::beman::net::detail::io_base* o) { + auto& cmp(*static_cast(o)); + cmp.complete(); + return ::beman::net::detail::submit_result::submit; + }; + return this->add_outstanding(op); + } auto accept(::beman::net::detail::context_base::accept_operation* completion) -> ::beman::net::detail::submit_result override final { completion->work = [](::beman::net::detail::context_base& ctxt, ::beman::net::detail::io_base* comp) { From ecabdc4afa75b9ed4852d68abfa2d98c41c336e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 19 Apr 2026 15:40:36 +0100 Subject: [PATCH 04/25] fixed the code to have poll() work for the poll_context --- examples/populate-postgres.cpp | 29 ++- examples/postgres.cpp | 187 +++++++++--------- include/beman/net/detail/basic_socket.hpp | 2 +- include/beman/net/detail/context_base.hpp | 4 +- include/beman/net/detail/event_type.hpp | 12 +- .../beman/net/detail/io_context_scheduler.hpp | 3 + include/beman/net/detail/operations.hpp | 15 +- include/beman/net/detail/poll_context.hpp | 1 + 8 files changed, 126 insertions(+), 127 deletions(-) diff --git a/examples/populate-postgres.cpp b/examples/populate-postgres.cpp index 606aff1..5c8baa9 100644 --- a/examples/populate-postgres.cpp +++ b/examples/populate-postgres.cpp @@ -9,29 +9,28 @@ #include namespace pq { - using connection = std::unique_ptr; - using result = std::unique_ptr; +using connection = std::unique_ptr; +using result = std::unique_ptr; - struct error { - std::string msg; - error(const connection& conn) : msg(PQerrorMessage(conn.get())) {} - const char* what() const noexcept { return msg.c_str(); }; - friend std::ostream& operator<< (std::ostream& os, const error& err) { - return os << err.msg; - } - }; -} +struct error { + std::string msg; + error(const connection& conn) : msg(PQerrorMessage(conn.get())) {} + const char* what() const noexcept { return msg.c_str(); }; + friend std::ostream& operator<<(std::ostream& os, const error& err) { return os << err.msg; } +}; +} // namespace pq int main() { std::cout << std::unitbuf; pq::connection conn(PQconnectdb("user=sruser dbname=sruser")); if (PQstatus(conn.get()) != CONNECTION_OK) { - std::cout << "Connection to database failed: " << pq::error(conn) << '\n'; ; + std::cout << "Connection to database failed: " << pq::error(conn) << '\n'; + ; return 1; } - const char*const query_version = "SELECT version(), pg_sleep(0)"; - pq::result res(PQexec(conn.get(), query_version)); + const char* const query_version = "SELECT version(), pg_sleep(0)"; + pq::result res(PQexec(conn.get(), query_version)); if (PQresultStatus(res.get()) != PGRES_TUPLES_OK) { std::cout << "SELECT failed: " << pq::error(conn) << '\n'; return 1; @@ -41,7 +40,7 @@ int main() { std::string input("/usr/share/dict/words"); std::cout << "populating from '" << input << "'\n"; std::ifstream file(input); - std::string message; + std::string message; for (std::size_t i{}; i < 100 && std::getline(file, message); ++i) { std::ostringstream ins; ins << "insert into messages (key, message) values(" << i << ", '" << message << "');"; diff --git a/examples/postgres.cpp b/examples/postgres.cpp index 1176a69..ee7800c 100644 --- a/examples/postgres.cpp +++ b/examples/postgres.cpp @@ -10,118 +10,117 @@ #include #include -namespace ex = beman::execution; +namespace ex = beman::execution; namespace net = beman::net; namespace pq { - struct connection { - using handle_t = std::unique_ptr; - - handle_t handle; - net::ip::tcp::socket socket; - connection(net::io_context& io, PGconn* conn): handle(conn), socket(io.get_scheduler().get_context(), io.make_socket(PQsocket(handle.get()))) {} +struct connection { + using handle_t = std::unique_ptr; + + handle_t handle; + net::ip::tcp::socket socket; + connection(net::io_context& io, PGconn* conn) + : handle(conn), socket(io.get_scheduler().get_context(), io.make_socket(PQsocket(handle.get()))) {} + + net::ip::tcp::socket& get_socket() { return this->socket; } + operator PGconn*() { return this->handle.get(); } + operator const PGconn*() const { return this->handle.get(); } +}; +using result = std::unique_ptr; + +struct error { + std::string msg; + explicit error(const char* m) : msg(m) {} + error(const connection& conn) : msg(PQerrorMessage(conn)) {} + const char* what() const noexcept { return msg.c_str(); }; + friend std::ostream& operator<<(std::ostream& os, const error& err) { return os << err.msg; } +}; + +// PQsetnonblocking(const PGconn *conn, int arg) - set non-blocking mode to avoid write blocks +// PQsocket(const PGconn *conn) - get socket +// PQflush(const PGconn *conn) - flush output buffer, return 1 if still pending data +// PQisBusy(const PGconn *conn) - PQgetResult() would block +// PQconsumeInput(const PGconn *conn) - consume available input, clear socket stat +// PQgetResult(const PGconn *conn) - get result, return nullptr if no more results, or would block +// PQsetSingleRowMode(PGconn *conn) - set single row mode, return 0 on failure +// PQsetChunkedMode(PGconn *conn, int arg) - set chunked mode, return 0 on failure + +struct exec { + using sender_concept = ex::sender_t; + using completion_signatures = ex::completion_signatures; + template + static consteval completion_signatures get_completion_signatures() noexcept { + return {}; + } - net::ip::tcp::socket& get_socket() { return this->socket; } - operator PGconn*() { return this->handle.get(); } - operator const PGconn*() const { return this->handle.get(); } - }; - using result = std::unique_ptr; - - struct error { - std::string msg; - explicit error(const char* m) : msg(m) {} - error(const connection& conn) : msg(PQerrorMessage(conn)) {} - const char* what() const noexcept { return msg.c_str(); }; - friend std::ostream& operator<< (std::ostream& os, const error& err) { - return os << err.msg; - } - }; + template + struct state { + using operation_state_concept = ex::operation_state_t; - // PQsetnonblocking(const PGconn *conn, int arg) - set non-blocking mode to avoid write blocks - // PQsocket(const PGconn *conn) - get socket - // PQflush(const PGconn *conn) - flush output buffer, return 1 if still pending data - // PQisBusy(const PGconn *conn) - PQgetResult() would block - // PQconsumeInput(const PGconn *conn) - consume available input, clear socket stat - // PQgetResult(const PGconn *conn) - get result, return nullptr if no more results, or would block - // PQsetSingleRowMode(PGconn *conn) - set single row mode, return 0 on failure - // PQsetChunkedMode(PGconn *conn, int arg) - set chunked mode, return 0 on failure - - struct exec { - using sender_concept = ex::sender_t; - using completion_signatures = ex::completion_signatures; - template - static consteval completion_signatures get_completion_signatures() noexcept { return {}; } - - template - struct state { - using operation_state_concept = ex::operation_state_t; - - struct env { - using error_types = ex::completion_signatures; - }; - static ex::task work(connection& conn, std::string query) noexcept { - std::cout << "exec.work()\n"; - if (!PQsendQuery(conn, query.c_str())) { - std::cout << "PQsendQuery failed: " << PQerrorMessage(conn) << "\n"; - co_yield ex::with_error(pq::error(conn)); - } - PQflush(conn); - while (PQisBusy(conn)) { - std::cout << "co_awaiting poll\n"; - auto evs = co_await net::async_poll(conn.get_socket(), net::event_type::in); - std::cout << "co_awaiting done=" << evs << "\n"; - if (!PQconsumeInput(conn)) { - std::cout << "PQconsumeInput failed: " << PQerrorMessage(conn) << "\n"; - co_yield ex::with_error(pq::error(conn)); - } - break; - } - if (pq::result res{pq::result(PQgetResult(conn))}) { - co_return std::move(res); - } - else { + struct env { + using error_types = ex::completion_signatures; + }; + static ex::task work(connection& conn, std::string query) noexcept { + std::cout << "exec.work()\n"; + if (!PQsendQuery(conn, query.c_str())) { + std::cout << "PQsendQuery failed: " << PQerrorMessage(conn) << "\n"; + co_yield ex::with_error(pq::error(conn)); + } + PQflush(conn); + while (PQisBusy(conn)) { + std::cout << "co_awaiting poll\n"; + auto evs = co_await net::async_poll(conn.get_socket(), net::event_type::in); + std::cout << "co_awaiting done=" << evs << "\n"; + if (!PQconsumeInput(conn)) { + std::cout << "PQconsumeInput failed: " << PQerrorMessage(conn) << "\n"; co_yield ex::with_error(pq::error(conn)); } - std::unreachable(); } - using inner_state_t = ex::connect_result_t(), std::string{})), Receiver&&>; + if (pq::result res{pq::result(PQgetResult(conn))}) { + co_return std::move(res); + } else { + co_yield ex::with_error(pq::error(conn)); + } + std::unreachable(); + } + using inner_state_t = + ex::connect_result_t(), std::string{})), Receiver&&>; - inner_state_t inner_state; + inner_state_t inner_state; - state(Receiver&& r, connection& conn, std::string query) - : inner_state(ex::connect(work(conn, std::move(query)), std::forward(r))) {} + state(Receiver&& r, connection& conn, std::string query) + : inner_state(ex::connect(work(conn, std::move(query)), std::forward(r))) {} - auto start() noexcept -> void { - ex::start(this->inner_state); - } - }; + auto start() noexcept -> void { ex::start(this->inner_state); } + }; - template - auto connect(Receiver&& receiver) { - return state{std::forward(receiver), conn, std::move(query)}; - } + template + auto connect(Receiver&& receiver) { + return state{std::forward(receiver), conn, std::move(query)}; + } - exec(connection& conn, std::string query) : conn(conn), query(std::move(query)) {} - connection& conn; - std::string query; - }; + exec(connection& conn, std::string query) : conn(conn), query(std::move(query)) {} + connection& conn; + std::string query; +}; - inline constexpr double sleep_time = 3.0; -} +inline constexpr double sleep_time = 3.0; +} // namespace pq int main() { std::cout << std::unitbuf; net::io_context io; std::cout << "connecting\n"; pq::connection conn(io, PQconnectdb("user=sruser dbname=sruser")); - PQsetnonblocking(conn, 1); + // PQsetnonblocking(conn, 1); std::cout << "connection created\n"; if (PQstatus(conn) != CONNECTION_OK) { - std::cout << "Connection to database failed: " << pq::error(conn) << '\n'; ; + std::cout << "Connection to database failed: " << pq::error(conn) << '\n'; + ; return 1; } - [[maybe_unused]] const char*const query_version = "SELECT version(), pg_sleep(3)"; + [[maybe_unused]] const char* const query_version = "SELECT version(), pg_sleep(3)"; #if 0 pq::result res(PQexec(conn, query_version)); if (PQresultStatus(res.get()) != PGRES_TUPLES_OK) { @@ -131,14 +130,13 @@ int main() { std::cout << "PostgreSQL version: " << PQgetvalue(res.get(), 0, 0) << '\n'; #endif - std::string query( - "select *, pg_sleep(10.1) from messages where 0 <= key and key < 3;" - //"select *, pg_sleep(0.1) from messages where 3 <= key and key < 6;" - ); + std::string query("select *, pg_sleep(0.5) from messages where 0 <= key and key < 3;" + //"select *, pg_sleep(0.1) from messages where 3 <= key and key < 6;" + ); try { - [[maybe_unused]] auto result = ex::sync_wait(pq::exec(conn, query)); + [[maybe_unused]] auto result = ex::sync_wait(ex::when_all(pq::exec(conn, query), io.async_run())); if (result) { - auto[res] = *std::move(result); + auto [res] = *std::move(result); if (PQresultStatus(res.get()) != PGRES_TUPLES_OK) { std::cout << "SELECT failed: " << pq::error(conn) << '\n'; return 1; @@ -150,8 +148,7 @@ int main() { std::cout << '\n'; } } - } - catch (const pq::error& e) { + } catch (const pq::error& e) { std::cout << "Error: " << e << '\n'; } } diff --git a/include/beman/net/detail/basic_socket.hpp b/include/beman/net/detail/basic_socket.hpp index b324c33..2b2cabd 100644 --- a/include/beman/net/detail/basic_socket.hpp +++ b/include/beman/net/detail/basic_socket.hpp @@ -21,7 +21,7 @@ class beman::net::basic_socket : public ::beman::net::socket_base { private: ::beman::net::detail::context_base* d_context; - protocol_type d_protocol{::beman::net::ip::tcp::v6()}; //-dk:TODO should initialize based on protocol_type + protocol_type d_protocol{::beman::net::ip::tcp::v6()}; //-dk:TODO should initialize based on protocol_type ::beman::net::detail::socket_id d_id{::beman::net::detail::socket_id::invalid}; public: diff --git a/include/beman/net/detail/context_base.hpp b/include/beman/net/detail/context_base.hpp index 4cfc44d..cfccb9f 100644 --- a/include/beman/net/detail/context_base.hpp +++ b/include/beman/net/detail/context_base.hpp @@ -26,7 +26,7 @@ struct beman::net::detail::context_base { virtual auto complete() -> void = 0; }; - using poll_operation = ::beman::net::detail::io_operation<::std::tuple>; + using poll_operation = ::beman::net::detail::io_operation<::std::tuple>; using accept_operation = ::beman::net::detail::io_operation< ::std::tuple<::beman::net::detail::endpoint, ::socklen_t, ::std::optional<::beman::net::detail::socket_id>>>; using connect_operation = ::beman::net::detail::io_operation<::std::tuple<::beman::net::detail::endpoint>>; @@ -52,7 +52,7 @@ struct beman::net::detail::context_base { virtual auto cancel(::beman::net::detail::io_base*, ::beman::net::detail::io_base*) -> void = 0; virtual auto schedule(::beman::net::detail::context_base::task*) -> void = 0; - virtual auto poll(::beman::net::detail::context_base::poll_operation*) -> ::beman::net::detail::submit_result { return {}; }; //-dk:TODO = 0; + virtual auto poll(::beman::net::detail::context_base::poll_operation*) -> ::beman::net::detail::submit_result = 0; virtual auto accept(::beman::net::detail::context_base::accept_operation*) -> ::beman::net::detail::submit_result = 0; virtual auto connect(::beman::net::detail::context_base::connect_operation*) diff --git a/include/beman/net/detail/event_type.hpp b/include/beman/net/detail/event_type.hpp index ac5ed18..a25097d 100644 --- a/include/beman/net/detail/event_type.hpp +++ b/include/beman/net/detail/event_type.hpp @@ -15,16 +15,16 @@ constexpr ::beman::net::event_type operator&(::beman::net::event_type e0, ::bema return ::beman::net::event_type(::std::uint8_t(e0) & ::std::uint8_t(e1)); } inline std::ostream& operator<<(std::ostream& os, ::beman::net::event_type e) { - switch (e) - {default: + switch (e) { + default: return os << "invalid(" << ::std::uint8_t(e) << ")"; - case ::beman::net::event_type::none: + case ::beman::net::event_type::none: return os << "none"; - case ::beman::net::event_type::in: + case ::beman::net::event_type::in: return os << "in"; - case ::beman::net::event_type::out: + case ::beman::net::event_type::out: return os << "out"; - case ::beman::net::event_type::in_out: + case ::beman::net::event_type::in_out: return os << "in|out"; } } diff --git a/include/beman/net/detail/io_context_scheduler.hpp b/include/beman/net/detail/io_context_scheduler.hpp index 872c6f8..040755e 100644 --- a/include/beman/net/detail/io_context_scheduler.hpp +++ b/include/beman/net/detail/io_context_scheduler.hpp @@ -76,6 +76,9 @@ class beman::net::detail::io_context_scheduler { auto cancel(beman::net::detail::io_base* cancel_op, beman::net::detail::io_base* op) -> void { this->d_context->cancel(cancel_op, op); } + auto poll(::beman::net::detail::context_base::poll_operation* op) -> ::beman::net::detail::submit_result { + return this->d_context->poll(op); + } auto accept(::beman::net::detail::context_base::accept_operation* op) -> ::beman::net::detail::submit_result { return this->d_context->accept(op); } diff --git a/include/beman/net/detail/operations.hpp b/include/beman/net/detail/operations.hpp index 7aedb75..b9b94e7 100644 --- a/include/beman/net/detail/operations.hpp +++ b/include/beman/net/detail/operations.hpp @@ -52,22 +52,21 @@ struct beman::net::detail::poll_desc { struct data { using completion_signature = ::beman::net::detail::ex::set_value_t(::beman::net::event_type); - Socket& d_socket; + Socket& d_socket; ::beman::net::event_type d_mask; - data(Socket& socket, ::beman::net::event_type mask): d_socket(socket), d_mask(mask) {} + data(Socket& socket, ::beman::net::event_type mask) : d_socket(socket), d_mask(mask) {} auto id() const { return this->d_socket.id(); } auto events() const -> ::beman::net::event_type { return this->d_mask; } auto set_value([[maybe_unused]] operation& o, auto&& receiver) { - ::beman::net::detail::ex::set_value( - ::std::move(receiver), - //::std::get<0>(o) - ::beman::net::event_type{} - ); + ::beman::net::detail::ex::set_value(::std::move(receiver), + //::std::get<0>(o) + ::beman::net::event_type{}); } auto get_scheduler() { return this->d_socket.get_scheduler(); } auto submit([[maybe_unused]] auto* base) -> ::beman::net::detail::submit_result { - return ::beman::net::detail::submit_result::ready; + ::std::get<1>(*base) = this->d_mask; + return this->get_scheduler().poll(base); } }; }; diff --git a/include/beman/net/detail/poll_context.hpp b/include/beman/net/detail/poll_context.hpp index ca88d0d..7537aa9 100644 --- a/include/beman/net/detail/poll_context.hpp +++ b/include/beman/net/detail/poll_context.hpp @@ -16,6 +16,7 @@ #include #include #include +#include //-dk:TODO remove // ---------------------------------------------------------------------------- From 5d39b751fbd593da8383ae2e40fdf113663e05e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 19 Apr 2026 16:30:10 +0100 Subject: [PATCH 05/25] making progress on querying the database in a nice way --- examples/postgres.cpp | 74 +++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/examples/postgres.cpp b/examples/postgres.cpp index ee7800c..897c8c4 100644 --- a/examples/postgres.cpp +++ b/examples/postgres.cpp @@ -9,24 +9,28 @@ #include #include #include +#include namespace ex = beman::execution; namespace net = beman::net; -namespace pq { +namespace pg { struct connection { using handle_t = std::unique_ptr; handle_t handle; net::ip::tcp::socket socket; connection(net::io_context& io, PGconn* conn) - : handle(conn), socket(io.get_scheduler().get_context(), io.make_socket(PQsocket(handle.get()))) {} + : handle(conn), socket(io.get_scheduler().get_context(), io.make_socket(PQsocket(handle.get()))) { + if (PQstatus(conn) != CONNECTION_OK) { + throw std::runtime_error(std::string("Connection to database failed: ") + PQerrorMessage(conn)); + } + } net::ip::tcp::socket& get_socket() { return this->socket; } operator PGconn*() { return this->handle.get(); } operator const PGconn*() const { return this->handle.get(); } }; -using result = std::unique_ptr; struct error { std::string msg; @@ -36,6 +40,8 @@ struct error { friend std::ostream& operator<<(std::ostream& os, const error& err) { return os << err.msg; } }; +using result = std::unique_ptr; + // PQsetnonblocking(const PGconn *conn, int arg) - set non-blocking mode to avoid write blocks // PQsocket(const PGconn *conn) - get socket // PQflush(const PGconn *conn) - flush output buffer, return 1 if still pending data @@ -64,7 +70,7 @@ struct exec { std::cout << "exec.work()\n"; if (!PQsendQuery(conn, query.c_str())) { std::cout << "PQsendQuery failed: " << PQerrorMessage(conn) << "\n"; - co_yield ex::with_error(pq::error(conn)); + co_yield ex::with_error(pg::error(conn)); } PQflush(conn); while (PQisBusy(conn)) { @@ -73,13 +79,13 @@ struct exec { std::cout << "co_awaiting done=" << evs << "\n"; if (!PQconsumeInput(conn)) { std::cout << "PQconsumeInput failed: " << PQerrorMessage(conn) << "\n"; - co_yield ex::with_error(pq::error(conn)); + co_yield ex::with_error(pg::error(conn)); } } - if (pq::result res{pq::result(PQgetResult(conn))}) { + if (pg::result res{pg::result(PQgetResult(conn))}) { co_return std::move(res); } else { - co_yield ex::with_error(pq::error(conn)); + co_yield ex::with_error(pg::error(conn)); } std::unreachable(); } @@ -105,50 +111,36 @@ struct exec { }; inline constexpr double sleep_time = 3.0; -} // namespace pq +} // namespace pg int main() { std::cout << std::unitbuf; - net::io_context io; - std::cout << "connecting\n"; - pq::connection conn(io, PQconnectdb("user=sruser dbname=sruser")); - // PQsetnonblocking(conn, 1); - std::cout << "connection created\n"; - - if (PQstatus(conn) != CONNECTION_OK) { - std::cout << "Connection to database failed: " << pq::error(conn) << '\n'; - ; - return 1; - } - [[maybe_unused]] const char* const query_version = "SELECT version(), pg_sleep(3)"; -#if 0 - pq::result res(PQexec(conn, query_version)); - if (PQresultStatus(res.get()) != PGRES_TUPLES_OK) { - std::cout << "SELECT failed: " << pq::error(conn) << '\n'; - return 1; - } - std::cout << "PostgreSQL version: " << PQgetvalue(res.get(), 0, 0) << '\n'; -#endif - - std::string query("select *, pg_sleep(0.5) from messages where 0 <= key and key < 3;" - //"select *, pg_sleep(0.1) from messages where 3 <= key and key < 6;" - ); try { - [[maybe_unused]] auto result = ex::sync_wait(ex::when_all(pq::exec(conn, query), io.async_run())); - if (result) { - auto [res] = *std::move(result); - if (PQresultStatus(res.get()) != PGRES_TUPLES_OK) { - std::cout << "SELECT failed: " << pq::error(conn) << '\n'; - return 1; - } + net::io_context io; + pg::connection conn(io, PQconnectdb("user=sruser dbname=sruser")); + ex::counting_scope scope; + ex::run_loop loop; + + auto spawn{[&](ex::sender auto s){ + ex::spawn( + ex::write_env(std::move(s), ex::env{ex::prop{ex::get_scheduler, loop.get_scheduler()}}), + scope.get_token()); + }}; + + std::string query("select *, pg_sleep(0.5) from messages where 0 <= key and key < 3;"); + spawn(pg::exec(conn, query) | ex::then([](pg::result res) noexcept { for (int i{}, n{PQntuples(res.get())}; i < n; ++i) { for (int j{}, m{PQnfields(res.get())}; j < m; ++j) { std::cout << PQgetvalue(res.get(), i, j) << ','; } std::cout << '\n'; } - } - } catch (const pq::error& e) { + }) | ex::upon_error([](pg::error error) noexcept { + std::cout << "error: " << error << "\n"; + })); + + ex::sync_wait(ex::when_all(scope.join(), io.async_run()/*-dk:TODO , loop.run() */)); + } catch (const pg::error& e) { std::cout << "Error: " << e << '\n'; } } From aa136c7a0be22b961bc183418089c7f74a0fb927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 19 Apr 2026 19:10:39 +0100 Subject: [PATCH 06/25] added sync_run to run a local run_loop --- examples/postgres.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/examples/postgres.cpp b/examples/postgres.cpp index 897c8c4..1758f33 100644 --- a/examples/postgres.cpp +++ b/examples/postgres.cpp @@ -113,6 +113,26 @@ struct exec { inline constexpr double sleep_time = 3.0; } // namespace pg +namespace { + auto sync_run(ex::run_loop& loop, ex::sender auto snd) { + struct env { + ex::run_loop& loop; + auto query(ex::get_scheduler_t const&) const noexcept { return this->loop.get_scheduler(); } + }; + struct receiver { + ex::run_loop& loop; + using receiver_concept = ex::receiver_t; + + auto get_env() const noexcept { return env{this->loop}; } + auto set_value() noexcept { this->loop.finish();} + }; + + auto state{ex::connect(std::move(snd), receiver{loop})}; + ex::start(state); + loop.run(); + } +} + int main() { std::cout << std::unitbuf; try { @@ -139,7 +159,10 @@ int main() { std::cout << "error: " << error << "\n"; })); - ex::sync_wait(ex::when_all(scope.join(), io.async_run()/*-dk:TODO , loop.run() */)); + sync_run(loop, ex::when_all(scope.join(), io.async_run()) + | ex::upon_error([](auto&&) noexcept {}) + | ex::upon_stopped([]() noexcept {}) + ); } catch (const pg::error& e) { std::cout << "Error: " << e << '\n'; } From 958a6ee75f3108cc26127fbb9d2e705a9be85575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 19 Apr 2026 23:01:32 +0100 Subject: [PATCH 07/25] trying to fix the run_loop existing on a timer --- examples/postgres.cpp | 22 +++++++++++++++++++++- include/beman/net/detail/io_context.hpp | 6 ++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/examples/postgres.cpp b/examples/postgres.cpp index 1758f33..7da1c46 100644 --- a/examples/postgres.cpp +++ b/examples/postgres.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,7 @@ namespace ex = beman::execution; namespace net = beman::net; +using namespace std::chrono_literals; namespace pg { struct connection { @@ -129,7 +131,9 @@ namespace { auto state{ex::connect(std::move(snd), receiver{loop})}; ex::start(state); + std::cout << "running loop\n"; loop.run(); + std::cout << "running loop done\n"; } } @@ -146,7 +150,22 @@ int main() { ex::write_env(std::move(s), ex::env{ex::prop{ex::get_scheduler, loop.get_scheduler()}}), scope.get_token()); }}; + spawn(std::invoke([](auto sched)->ex::task<> { + while (true) { + std::cout << "\rtime=" << std::chrono::system_clock::now() << "\n" << std::flush; + co_await net::resume_after(sched, 1s); + } + }, io.get_scheduler()) | ex::upon_error([](auto&&) noexcept {})); + + ex::sync_wait(ex::when_all(std::invoke([](auto sched)->ex::task<> { + while (true) { + std::cout << "\rtime=" << std::chrono::system_clock::now() << "\n" << std::flush; + co_await net::resume_after(sched, 1s); + } + }, io.get_scheduler()), + io.async_run_unstopped())); + if (false) { std::string query("select *, pg_sleep(0.5) from messages where 0 <= key and key < 3;"); spawn(pg::exec(conn, query) | ex::then([](pg::result res) noexcept { for (int i{}, n{PQntuples(res.get())}; i < n; ++i) { @@ -158,8 +177,9 @@ int main() { }) | ex::upon_error([](pg::error error) noexcept { std::cout << "error: " << error << "\n"; })); + } - sync_run(loop, ex::when_all(scope.join(), io.async_run()) + sync_run(loop, ex::when_all(scope.join(), io.async_run_unstopped() | ex::then([]() noexcept { std::cout << "async_run done\n"; })) | ex::upon_error([](auto&&) noexcept {}) | ex::upon_stopped([]() noexcept {}) ); diff --git a/include/beman/net/detail/io_context.hpp b/include/beman/net/detail/io_context.hpp index 35cb836..f08c141 100644 --- a/include/beman/net/detail/io_context.hpp +++ b/include/beman/net/detail/io_context.hpp @@ -137,6 +137,12 @@ class beman::net::io_context { [&last_count] { return last_count == 0; }); }); } + auto async_run_unstopped() { + return beman::net::repeat_effect_until( + beman::execution::just(), + this->async_run_one() | beman::execution::then([](auto){}), + [] { return false; }); + } ::std::size_t run_one() noexcept { return this->d_context.run_one(); } ::std::size_t run() { ::std::size_t count{}; From 35bd5dcf86eff43f563addfba3d204145c301874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Mon, 20 Apr 2026 20:50:49 +0100 Subject: [PATCH 08/25] made some progress on fixing async_run --- examples/postgres.cpp | 47 +++++++++++++++++-------- examples/postgres.txt | 15 ++++++++ include/beman/net/detail/io_context.hpp | 22 ++++++------ include/beman/net/detail/scope.hpp | 3 +- 4 files changed, 60 insertions(+), 27 deletions(-) create mode 100644 examples/postgres.txt diff --git a/examples/postgres.cpp b/examples/postgres.cpp index 7da1c46..aee3036 100644 --- a/examples/postgres.cpp +++ b/examples/postgres.cpp @@ -116,20 +116,19 @@ inline constexpr double sleep_time = 3.0; } // namespace pg namespace { - auto sync_run(ex::run_loop& loop, ex::sender auto snd) { - struct env { - ex::run_loop& loop; - auto query(ex::get_scheduler_t const&) const noexcept { return this->loop.get_scheduler(); } - }; - struct receiver { - ex::run_loop& loop; - using receiver_concept = ex::receiver_t; - - auto get_env() const noexcept { return env{this->loop}; } - auto set_value() noexcept { this->loop.finish();} - }; + struct sync_run_env { + ex::run_loop& loop; + auto query(ex::get_scheduler_t const&) const noexcept { return this->loop.get_scheduler(); } + }; + struct sync_run_receiver { + ex::run_loop& loop; + using receiver_concept = ex::receiver_t; - auto state{ex::connect(std::move(snd), receiver{loop})}; + auto get_env() const noexcept { return sync_run_env{this->loop}; } + auto set_value() noexcept { this->loop.finish();} + }; + auto sync_run(ex::run_loop& loop, ex::sender auto snd) { + auto state{ex::connect(std::move(snd), sync_run_receiver{loop})}; ex::start(state); std::cout << "running loop\n"; loop.run(); @@ -150,6 +149,12 @@ int main() { ex::write_env(std::move(s), ex::env{ex::prop{ex::get_scheduler, loop.get_scheduler()}}), scope.get_token()); }}; + spawn(ex::read_env(ex::get_scheduler) + | ex::then([](auto&&)noexcept {}) + | ex::upon_error([](auto&&)noexcept {}) + ); + + spawn(std::invoke([](auto sched)->ex::task<> { while (true) { std::cout << "\rtime=" << std::chrono::system_clock::now() << "\n" << std::flush; @@ -157,13 +162,14 @@ int main() { } }, io.get_scheduler()) | ex::upon_error([](auto&&) noexcept {})); +#if 0 ex::sync_wait(ex::when_all(std::invoke([](auto sched)->ex::task<> { while (true) { std::cout << "\rtime=" << std::chrono::system_clock::now() << "\n" << std::flush; co_await net::resume_after(sched, 1s); } }, io.get_scheduler()), - io.async_run_unstopped())); + io.async_run())); if (false) { std::string query("select *, pg_sleep(0.5) from messages where 0 <= key and key < 3;"); @@ -178,11 +184,22 @@ int main() { std::cout << "error: " << error << "\n"; })); } +#endif + + - sync_run(loop, ex::when_all(scope.join(), io.async_run_unstopped() | ex::then([]() noexcept { std::cout << "async_run done\n"; })) + + sync_run(loop, + io.async_run() + | ex::upon_error([](auto&&) noexcept {}) + | ex::upon_stopped([]() noexcept {}) + ); +#if 0 + sync_run(loop, ex::when_all(scope.join(), io.async_run() | ex::then([]() noexcept { std::cout << "async_run done\n"; })) | ex::upon_error([](auto&&) noexcept {}) | ex::upon_stopped([]() noexcept {}) ); +#endif } catch (const pg::error& e) { std::cout << "Error: " << e << '\n'; } diff --git a/examples/postgres.txt b/examples/postgres.txt new file mode 100644 index 0000000..5aa0a14 --- /dev/null +++ b/examples/postgres.txt @@ -0,0 +1,15 @@ +psql -U postgres +> create role sruser password 'sruser' createdb login; +> create database sruser; + +psql -d sruser -U postgres +> create table messages(key integer, message text); +> create unique index msg_index on messages(key); +> grant all on messages to sruser; + +psql -U sruser +> insert into messages (key, message) values(0, 'zero'); + +touch ~/.pgpass +chmod go-rwx ~/.pgpass +echo '*:*:*:sruser:sruser' > ~/.pgpass diff --git a/include/beman/net/detail/io_context.hpp b/include/beman/net/detail/io_context.hpp index f08c141..9170c34 100644 --- a/include/beman/net/detail/io_context.hpp +++ b/include/beman/net/detail/io_context.hpp @@ -128,21 +128,21 @@ class beman::net::io_context { auto async_run_one() { return run_one_sender{this}; } auto async_run() { - return beman::execution::let_value(beman::execution::just(), [this, last_count = std::size_t(1)]() mutable { + return beman::execution::let_value( + beman::execution::read_env(beman::execution::get_scheduler), + [this, last_count = std::size_t(1)]([[maybe_unused]] auto sched) mutable { (void)last_count; //-dk:TODO remove this once no compiler complains about last_count being unused return beman::net::repeat_effect_until( - beman::execution::just(), - [this] { return this->async_run_one(); }() | - beman::execution::then([&last_count](std::size_t count) { last_count = count; }), - [&last_count] { return last_count == 0; }); + beman::execution::just(), + beman::execution::starts_on(sched, + this->async_run_one() | + beman::execution::then([&last_count](std::size_t count) { last_count = count; }) + ) + , + [&last_count] { return last_count == 0; } + ); }); } - auto async_run_unstopped() { - return beman::net::repeat_effect_until( - beman::execution::just(), - this->async_run_one() | beman::execution::then([](auto){}), - [] { return false; }); - } ::std::size_t run_one() noexcept { return this->d_context.run_one(); } ::std::size_t run() { ::std::size_t count{}; diff --git a/include/beman/net/detail/scope.hpp b/include/beman/net/detail/scope.hpp index 8c02074..a3fb850 100644 --- a/include/beman/net/detail/scope.hpp +++ b/include/beman/net/detail/scope.hpp @@ -56,7 +56,8 @@ class beman::net::detail::scope { }; auto run() -> auto { - return beman::execution::when_all(this->_counting_scope.join(), this->_io_context.async_run()); + //-dk:TODO return beman::execution::when_all(this->_counting_scope.join(), this->_io_context.async_run()); + return beman::execution::when_all(this->_counting_scope.join()); } auto get_token() -> token { return {this}; } From 846e5e945c4056746e9f46b11597059540c5ad7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Wed, 22 Apr 2026 22:47:39 +0100 Subject: [PATCH 09/25] a first cut at an asynchronous postgres demo --- CMakeLists.txt | 4 +-- examples/postgres.cpp | 64 ++++++++++++++++++++----------------------- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d85c0c6..47019d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,9 +30,9 @@ include(FetchContent) FetchContent_Declare( task - # FETCHCONTENT_SOURCE_DIR_TASK ${CMAKE_SOURCE_DIR}/../task + # SOURCE_DIR ${CMAKE_SOURCE_DIR}/../task GIT_REPOSITORY https://github.com/bemanproject/task - GIT_TAG 16a5916 + GIT_TAG abbb977 FIND_PACKAGE_ARGS 0.2.0 EXACT NAMES beman.task COMPONENTS task_headers ) FetchContent_MakeAvailable(task) diff --git a/examples/postgres.cpp b/examples/postgres.cpp index aee3036..ce7f79f 100644 --- a/examples/postgres.cpp +++ b/examples/postgres.cpp @@ -66,7 +66,7 @@ struct exec { using operation_state_concept = ex::operation_state_t; struct env { - using error_types = ex::completion_signatures; + using error_types = ex::completion_signatures; }; static ex::task work(connection& conn, std::string query) noexcept { std::cout << "exec.work()\n"; @@ -146,14 +146,11 @@ int main() { auto spawn{[&](ex::sender auto s){ ex::spawn( - ex::write_env(std::move(s), ex::env{ex::prop{ex::get_scheduler, loop.get_scheduler()}}), + ex::starts_on(loop.get_scheduler(), std::move(s)) + | ex::upon_error([](auto&&) noexcept {}) + , scope.get_token()); }}; - spawn(ex::read_env(ex::get_scheduler) - | ex::then([](auto&&)noexcept {}) - | ex::upon_error([](auto&&)noexcept {}) - ); - spawn(std::invoke([](auto sched)->ex::task<> { while (true) { @@ -162,44 +159,43 @@ int main() { } }, io.get_scheduler()) | ex::upon_error([](auto&&) noexcept {})); -#if 0 - ex::sync_wait(ex::when_all(std::invoke([](auto sched)->ex::task<> { - while (true) { - std::cout << "\rtime=" << std::chrono::system_clock::now() << "\n" << std::flush; - co_await net::resume_after(sched, 1s); - } - }, io.get_scheduler()), - io.async_run())); - - if (false) { + if (true) { std::string query("select *, pg_sleep(0.5) from messages where 0 <= key and key < 3;"); - spawn(pg::exec(conn, query) | ex::then([](pg::result res) noexcept { + spawn(pg::exec(conn, query) +#if 0 + | ex::then([](auto&&...) noexcept { std::cout << "query sent\n"; }) +#else + | ex::then([&](pg::result res, auto&&...) noexcept { for (int i{}, n{PQntuples(res.get())}; i < n; ++i) { for (int j{}, m{PQnfields(res.get())}; j < m; ++j) { std::cout << PQgetvalue(res.get(), i, j) << ','; } std::cout << '\n'; } - }) | ex::upon_error([](pg::error error) noexcept { - std::cout << "error: " << error << "\n"; + std::cout << "query done\n" << std::flush; + scope.request_stop(); + }) +#endif + | ex::upon_error([](auto error) noexcept { + if constexpr (std::same_as) { + std::cout << "query error: " << error << "\n"; + } if constexpr (std::same_as) { + std::cout << "exception\n"; + } else { + std::cout << "unexpected error: " << error << "\n"; + } })); } -#endif - - - - sync_run(loop, - io.async_run() - | ex::upon_error([](auto&&) noexcept {}) - | ex::upon_stopped([]() noexcept {}) - ); -#if 0 - sync_run(loop, ex::when_all(scope.join(), io.async_run() | ex::then([]() noexcept { std::cout << "async_run done\n"; })) - | ex::upon_error([](auto&&) noexcept {}) - | ex::upon_stopped([]() noexcept {}) + sync_run(loop, ex::when_all( + scope.join() + | ex::upon_error([](auto&&) noexcept {}) + , + io.async_run() | ex::then([]() noexcept { std::cout << "async_run done\n"; }) + | ex::upon_error([](auto&&) noexcept {}) + ) + | ex::upon_stopped([]() noexcept {}) ); -#endif } catch (const pg::error& e) { std::cout << "Error: " << e << '\n'; } From 7e5a81c2d1796288b9abeca8711d3d820089121b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Wed, 22 Apr 2026 23:30:10 +0100 Subject: [PATCH 10/25] clang-format and some refinement to improve set_error completions --- examples/postgres.cpp | 117 +++++++++------------- include/beman/net/detail/basic_socket.hpp | 2 +- include/beman/net/detail/context_base.hpp | 4 +- include/beman/net/detail/io_context.hpp | 28 +++--- 4 files changed, 67 insertions(+), 84 deletions(-) diff --git a/examples/postgres.cpp b/examples/postgres.cpp index ce7f79f..54a0a94 100644 --- a/examples/postgres.cpp +++ b/examples/postgres.cpp @@ -66,9 +66,10 @@ struct exec { using operation_state_concept = ex::operation_state_t; struct env { - using error_types = ex::completion_signatures; + using error_types = ex::completion_signatures; }; - static ex::task work(connection& conn, std::string query) noexcept { + static ex::task work([[maybe_unused]] connection& conn, + [[maybe_unused]] std::string query) noexcept { std::cout << "exec.work()\n"; if (!PQsendQuery(conn, query.c_str())) { std::cout << "PQsendQuery failed: " << PQerrorMessage(conn) << "\n"; @@ -116,25 +117,25 @@ inline constexpr double sleep_time = 3.0; } // namespace pg namespace { - struct sync_run_env { - ex::run_loop& loop; - auto query(ex::get_scheduler_t const&) const noexcept { return this->loop.get_scheduler(); } - }; - struct sync_run_receiver { - ex::run_loop& loop; - using receiver_concept = ex::receiver_t; +struct sync_run_env { + ex::run_loop& loop; + auto query(const ex::get_scheduler_t&) const noexcept { return this->loop.get_scheduler(); } +}; +struct sync_run_receiver { + ex::run_loop& loop; + using receiver_concept = ex::receiver_t; - auto get_env() const noexcept { return sync_run_env{this->loop}; } - auto set_value() noexcept { this->loop.finish();} - }; - auto sync_run(ex::run_loop& loop, ex::sender auto snd) { - auto state{ex::connect(std::move(snd), sync_run_receiver{loop})}; - ex::start(state); - std::cout << "running loop\n"; - loop.run(); - std::cout << "running loop done\n"; - } + auto get_env() const noexcept { return sync_run_env{this->loop}; } + auto set_value() noexcept { this->loop.finish(); } +}; +auto sync_run(ex::run_loop& loop, ex::sender auto snd) { + auto state{ex::connect(std::move(snd), sync_run_receiver{loop})}; + ex::start(state); + std::cout << "running loop\n"; + loop.run(); + std::cout << "running loop done\n"; } +} // namespace int main() { std::cout << std::unitbuf; @@ -144,58 +145,40 @@ int main() { ex::counting_scope scope; ex::run_loop loop; - auto spawn{[&](ex::sender auto s){ - ex::spawn( - ex::starts_on(loop.get_scheduler(), std::move(s)) - | ex::upon_error([](auto&&) noexcept {}) - , - scope.get_token()); + auto spawn{[&](ex::sender auto s) { + ex::spawn(ex::starts_on(loop.get_scheduler(), std::move(s)) | ex::upon_error([](auto&&) noexcept {}), + scope.get_token()); }}; - spawn(std::invoke([](auto sched)->ex::task<> { - while (true) { - std::cout << "\rtime=" << std::chrono::system_clock::now() << "\n" << std::flush; - co_await net::resume_after(sched, 1s); - } - }, io.get_scheduler()) | ex::upon_error([](auto&&) noexcept {})); + spawn(std::invoke( + [](auto sched) -> ex::task<> { + while (true) { + std::cout << "\rtime=" << std::chrono::system_clock::now() << "\n" << std::flush; + co_await net::resume_after(sched, 1s); + } + }, + io.get_scheduler()) | + ex::upon_error([](auto&&) noexcept {})); - if (true) { std::string query("select *, pg_sleep(0.5) from messages where 0 <= key and key < 3;"); - spawn(pg::exec(conn, query) -#if 0 - | ex::then([](auto&&...) noexcept { std::cout << "query sent\n"; }) -#else - | ex::then([&](pg::result res, auto&&...) noexcept { - for (int i{}, n{PQntuples(res.get())}; i < n; ++i) { - for (int j{}, m{PQnfields(res.get())}; j < m; ++j) { - std::cout << PQgetvalue(res.get(), i, j) << ','; - } - std::cout << '\n'; - } - std::cout << "query done\n" << std::flush; - scope.request_stop(); - }) -#endif - | ex::upon_error([](auto error) noexcept { - if constexpr (std::same_as) { - std::cout << "query error: " << error << "\n"; - } if constexpr (std::same_as) { - std::cout << "exception\n"; - } else { - std::cout << "unexpected error: " << error << "\n"; - } - })); - } - - sync_run(loop, ex::when_all( - scope.join() - | ex::upon_error([](auto&&) noexcept {}) - , - io.async_run() | ex::then([]() noexcept { std::cout << "async_run done\n"; }) - | ex::upon_error([](auto&&) noexcept {}) - ) - | ex::upon_stopped([]() noexcept {}) - ); + spawn(pg::exec(conn, query) | ex::then([&](pg::result res, auto&&...) noexcept { + for (int i{}, n{PQntuples(res.get())}; i < n; ++i) { + for (int j{}, m{PQnfields(res.get())}; j < m; ++j) { + std::cout << PQgetvalue(res.get(), i, j) << ','; + } + std::cout << '\n'; + } + std::cout << "query done\n" << std::flush; + scope.request_stop(); + }) | + ex::upon_error([](pg::error error) noexcept { std::cout << "query error: " << error << "\n"; })); + + sync_run(loop, + ex::when_all(scope.join(), io.async_run() + // | ex::then([]() noexcept { std::cout << "async_run done\n"; }) + //| ex::upon_error([](auto&&) noexcept {}) + ) | + ex::upon_stopped([]() noexcept {})); } catch (const pg::error& e) { std::cout << "Error: " << e << '\n'; } diff --git a/include/beman/net/detail/basic_socket.hpp b/include/beman/net/detail/basic_socket.hpp index 2b2cabd..d9353fc 100644 --- a/include/beman/net/detail/basic_socket.hpp +++ b/include/beman/net/detail/basic_socket.hpp @@ -22,7 +22,7 @@ class beman::net::basic_socket : public ::beman::net::socket_base { private: ::beman::net::detail::context_base* d_context; protocol_type d_protocol{::beman::net::ip::tcp::v6()}; //-dk:TODO should initialize based on protocol_type - ::beman::net::detail::socket_id d_id{::beman::net::detail::socket_id::invalid}; + ::beman::net::detail::socket_id d_id{::beman::net::detail::socket_id::invalid}; public: basic_socket() : d_context(nullptr) {} diff --git a/include/beman/net/detail/context_base.hpp b/include/beman/net/detail/context_base.hpp index cfccb9f..961dbf9 100644 --- a/include/beman/net/detail/context_base.hpp +++ b/include/beman/net/detail/context_base.hpp @@ -50,8 +50,8 @@ struct beman::net::detail::context_base { virtual auto run_one() noexcept -> ::std::size_t = 0; - virtual auto cancel(::beman::net::detail::io_base*, ::beman::net::detail::io_base*) -> void = 0; - virtual auto schedule(::beman::net::detail::context_base::task*) -> void = 0; + virtual auto cancel(::beman::net::detail::io_base*, ::beman::net::detail::io_base*) -> void = 0; + virtual auto schedule(::beman::net::detail::context_base::task*) -> void = 0; virtual auto poll(::beman::net::detail::context_base::poll_operation*) -> ::beman::net::detail::submit_result = 0; virtual auto accept(::beman::net::detail::context_base::accept_operation*) -> ::beman::net::detail::submit_result = 0; diff --git a/include/beman/net/detail/io_context.hpp b/include/beman/net/detail/io_context.hpp index 9170c34..1fc2a90 100644 --- a/include/beman/net/detail/io_context.hpp +++ b/include/beman/net/detail/io_context.hpp @@ -128,20 +128,20 @@ class beman::net::io_context { auto async_run_one() { return run_one_sender{this}; } auto async_run() { - return beman::execution::let_value( - beman::execution::read_env(beman::execution::get_scheduler), - [this, last_count = std::size_t(1)]([[maybe_unused]] auto sched) mutable { - (void)last_count; //-dk:TODO remove this once no compiler complains about last_count being unused - return beman::net::repeat_effect_until( - beman::execution::just(), - beman::execution::starts_on(sched, - this->async_run_one() | - beman::execution::then([&last_count](std::size_t count) { last_count = count; }) - ) - , - [&last_count] { return last_count == 0; } - ); - }); + return beman::execution::read_env(beman::execution::get_scheduler) | + beman::execution::let_value([this, last_count = std::size_t(1)](auto sched) mutable noexcept { + (void)last_count; //-dk:TODO remove this once no compiler complains about last_count being unused + return beman::net::repeat_effect_until( + beman::execution::just(), + beman::execution::starts_on( + sched, + this->async_run_one() | beman::execution::then([&last_count](std::size_t count) noexcept { + last_count = count; + })), + [&last_count]() noexcept { return last_count == 0; }); + }) | + beman::execution::upon_error([](auto&&) noexcept {}); + ; } ::std::size_t run_one() noexcept { return this->d_context.run_one(); } ::std::size_t run() { From fdb0cf5233a55b9a23b088b05ae2eca82165b981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Fri, 24 Apr 2026 22:30:52 +0100 Subject: [PATCH 11/25] added an initial print completions function --- examples/postgres.cpp | 77 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 10 deletions(-) diff --git a/examples/postgres.cpp b/examples/postgres.cpp index 54a0a94..83b82b4 100644 --- a/examples/postgres.cpp +++ b/examples/postgres.cpp @@ -135,10 +135,63 @@ auto sync_run(ex::run_loop& loop, ex::sender auto snd) { loop.run(); std::cout << "running loop done\n"; } + +template struct print_completion_signatures_t; +template +struct print_completion_signatures_t> { + void operator()(std::ostream& os) const { + ((os << typeid(Signatures).name() << ','), ...); + } +}; +template inline constexpr print_completion_signatures_t print_completion_signatures{}; + +struct print_completions_t { + template + struct sender { + using sender_concept = ex::sender_t; + template + static consteval auto get_completion_signatures() noexcept { + return ex::get_completion_signatures(); + } + + template + struct state { + using operation_state_concept = ex::operation_state_t; + using state_t = ex::connect_result_t; + using env_t = ex::env_of_t; + + state_t state_; + state(Receiver&& r, Sender&& s) + : state_(ex::connect(std::forward(s), std::forward(r))) + { + } + auto start() noexcept -> void { + std::cout << "completion_signatures<"; + print_completion_signatures())>(std::cout); + std::cout << ">\n"; + ex::start(this->state_); + } + }; + + std::remove_cvref_t sender; + template + auto connect(Receiver&& r) &&{ + return state{std::forward(r), std::move(sender)}; + } + }; + + template + auto operator()(Sender&& sndr) const { + return sender{std::forward(sndr)}; + } +}; + +inline constexpr print_completions_t print_completions{}; + } // namespace int main() { - std::cout << std::unitbuf; + std::cout << std::unitbuf << "PostgreSQL example\n"; try { net::io_context io; pg::connection conn(io, PQconnectdb("user=sruser dbname=sruser")); @@ -146,19 +199,26 @@ int main() { ex::run_loop loop; auto spawn{[&](ex::sender auto s) { - ex::spawn(ex::starts_on(loop.get_scheduler(), std::move(s)) | ex::upon_error([](auto&&) noexcept {}), + // ex::spawn(ex::starts_on(loop.get_scheduler(), std::move(s)) | ex::upon_error([](auto&&) noexcept {}), + // ex::spawn(print_completions(ex::starts_on(loop.get_scheduler(), std::move(s))) | ex::upon_error([](auto&&) noexcept {}), + ex::spawn(ex::starts_on(loop.get_scheduler(), print_completions(std::move(s))) | ex::upon_error([](auto&&) noexcept {}), + // ex::spawn(print_completions(ex::starts_on(loop.get_scheduler(), std::move(s))), scope.get_token()); }}; + struct noexcept_env { + using error_types = ex::completion_signatures<>; + }; + spawn(std::invoke( - [](auto sched) -> ex::task<> { + [](auto sched) noexcept -> ex::task { while (true) { std::cout << "\rtime=" << std::chrono::system_clock::now() << "\n" << std::flush; co_await net::resume_after(sched, 1s); } }, - io.get_scheduler()) | - ex::upon_error([](auto&&) noexcept {})); + io.get_scheduler()) + ); std::string query("select *, pg_sleep(0.5) from messages where 0 <= key and key < 3;"); spawn(pg::exec(conn, query) | ex::then([&](pg::result res, auto&&...) noexcept { @@ -174,11 +234,8 @@ int main() { ex::upon_error([](pg::error error) noexcept { std::cout << "query error: " << error << "\n"; })); sync_run(loop, - ex::when_all(scope.join(), io.async_run() - // | ex::then([]() noexcept { std::cout << "async_run done\n"; }) - //| ex::upon_error([](auto&&) noexcept {}) - ) | - ex::upon_stopped([]() noexcept {})); + ex::when_all(scope.join(), io.async_run()) + | ex::upon_stopped([]() noexcept {})); } catch (const pg::error& e) { std::cout << "Error: " << e << '\n'; } From 31b182d5ce274ce69c32eef7cf2a9b43ab8bef90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Mon, 27 Apr 2026 18:37:16 -0400 Subject: [PATCH 12/25] adding an example and building postgresql binding --- CMakeLists.txt | 7 +--- Makefile | 4 +- examples/CMakeLists.txt | 3 +- examples/populate-postgres.cpp | 2 +- examples/postgres.cpp | 56 +++++++++++-------------- include/beman/net/detail/io_context.hpp | 2 +- 6 files changed, 33 insertions(+), 41 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 47019d7..d05cba1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,9 +18,8 @@ option( option( BEMAN_NET_BUILD_EXAMPLES "Enable building examples. Default: ON. Values: { ON, OFF }." - TRUE + ${PROJECT_IS_TOP_LEVEL} ) -# ${PROJECT_IS_TOP_LEVEL} if(LINUX) option(BEMAN_NET_WITH_URING "Enable liburing io context" OFF) @@ -32,7 +31,7 @@ FetchContent_Declare( task # SOURCE_DIR ${CMAKE_SOURCE_DIR}/../task GIT_REPOSITORY https://github.com/bemanproject/task - GIT_TAG abbb977 + GIT_TAG 6ff5b89 FIND_PACKAGE_ARGS 0.2.0 EXACT NAMES beman.task COMPONENTS task_headers ) FetchContent_MakeAvailable(task) @@ -53,8 +52,6 @@ if(BEMAN_NET_BUILD_TESTS) add_subdirectory(tests/beman/net) endif() -message(NOTICE "Message test") -set(BEMAN_NET_BUILD_EXAMPLES TRUE) if(${BEMAN_NET_BUILD_EXAMPLES}) message(NOTICE "Building examples") add_subdirectory(examples) diff --git a/Makefile b/Makefile index 1bc894c..70bcb83 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception SANITIZERS = none debug msan asan usan tsan -.PHONY: default gcc clang run update check ce todo distclean clean build test all format $(SANITIZERS) +.PHONY: default gcc clang run update check ce todo distclean clean build test all clang-format format $(SANITIZERS) COMPILER=system CXX_BASE=$(CXX:$(dir $(CXX))%=%) @@ -89,7 +89,7 @@ check: todo: bin/mk-todo.py -format: +clang-format format: git clang-format main clean: diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 25fdd1b..e52e937 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -30,12 +30,13 @@ set(EXAMPLES milano http-munich postgres + populate-postgres server taps task ) set(xEXAMPLES taps) -set(EXAMPLES postgres populate-postgres) +set(xEXAMPLES postgres populate-postgres) foreach(EXAMPLE ${EXAMPLES}) set(EXAMPLE_TARGET beman.net.examples.${EXAMPLE}) diff --git a/examples/populate-postgres.cpp b/examples/populate-postgres.cpp index 5c8baa9..b14c30c 100644 --- a/examples/populate-postgres.cpp +++ b/examples/populate-postgres.cpp @@ -41,7 +41,7 @@ int main() { std::cout << "populating from '" << input << "'\n"; std::ifstream file(input); std::string message; - for (std::size_t i{}; i < 100 && std::getline(file, message); ++i) { + for (std::size_t i{1}; i < 100 && std::getline(file, message); ++i) { std::ostringstream ins; ins << "insert into messages (key, message) values(" << i << ", '" << message << "');"; std::cout << "inserting: " << ins.str() << '\n'; diff --git a/examples/postgres.cpp b/examples/postgres.cpp index 83b82b4..d98ac13 100644 --- a/examples/postgres.cpp +++ b/examples/postgres.cpp @@ -28,6 +28,7 @@ struct connection { throw std::runtime_error(std::string("Connection to database failed: ") + PQerrorMessage(conn)); } } + connection(connection&&) noexcept = default; net::ip::tcp::socket& get_socket() { return this->socket; } operator PGconn*() { return this->handle.get(); } @@ -104,7 +105,7 @@ struct exec { }; template - auto connect(Receiver&& receiver) { + auto connect(Receiver&& receiver) && noexcept { return state{std::forward(receiver), conn, std::move(query)}; } @@ -136,19 +137,19 @@ auto sync_run(ex::run_loop& loop, ex::sender auto snd) { std::cout << "running loop done\n"; } -template struct print_completion_signatures_t; +template +struct print_completion_signatures_t; template struct print_completion_signatures_t> { - void operator()(std::ostream& os) const { - ((os << typeid(Signatures).name() << ','), ...); - } + void operator()(std::ostream& os) const { ((os << typeid(Signatures).name() << ','), ...); } }; -template inline constexpr print_completion_signatures_t print_completion_signatures{}; +template +inline constexpr print_completion_signatures_t print_completion_signatures{}; struct print_completions_t { template struct sender { - using sender_concept = ex::sender_t; + using sender_concept = ex::sender_t; template static consteval auto get_completion_signatures() noexcept { return ex::get_completion_signatures(); @@ -157,14 +158,12 @@ struct print_completions_t { template struct state { using operation_state_concept = ex::operation_state_t; - using state_t = ex::connect_result_t; - using env_t = ex::env_of_t; + using state_t = ex::connect_result_t; + using env_t = ex::env_of_t; state_t state_; state(Receiver&& r, Sender&& s) - : state_(ex::connect(std::forward(s), std::forward(r))) - { - } + : state_(ex::connect(std::forward(s), std::forward(r))) {} auto start() noexcept -> void { std::cout << "completion_signatures<"; print_completion_signatures())>(std::cout); @@ -175,7 +174,7 @@ struct print_completions_t { std::remove_cvref_t sender; template - auto connect(Receiver&& r) &&{ + auto connect(Receiver&& r) && { return state{std::forward(r), std::move(sender)}; } }; @@ -186,7 +185,7 @@ struct print_completions_t { } }; -inline constexpr print_completions_t print_completions{}; +[[maybe_unused]] inline constexpr print_completions_t print_completions{}; } // namespace @@ -199,28 +198,25 @@ int main() { ex::run_loop loop; auto spawn{[&](ex::sender auto s) { - // ex::spawn(ex::starts_on(loop.get_scheduler(), std::move(s)) | ex::upon_error([](auto&&) noexcept {}), - // ex::spawn(print_completions(ex::starts_on(loop.get_scheduler(), std::move(s))) | ex::upon_error([](auto&&) noexcept {}), - ex::spawn(ex::starts_on(loop.get_scheduler(), print_completions(std::move(s))) | ex::upon_error([](auto&&) noexcept {}), - // ex::spawn(print_completions(ex::starts_on(loop.get_scheduler(), std::move(s))), - scope.get_token()); + ex::spawn(ex::starts_on(loop.get_scheduler(), std::move(s)), scope.get_token()); }}; struct noexcept_env { using error_types = ex::completion_signatures<>; }; + spawn(ex::just()); spawn(std::invoke( - [](auto sched) noexcept -> ex::task { - while (true) { - std::cout << "\rtime=" << std::chrono::system_clock::now() << "\n" << std::flush; - co_await net::resume_after(sched, 1s); - } - }, - io.get_scheduler()) - ); + [](auto sched) noexcept -> ex::task { + while (true) { + std::cout << "\rtime=" << std::chrono::system_clock::now() << "\n" << std::flush; + co_await net::resume_after(sched, 1s); + } + }, + io.get_scheduler())); + + std::string query("select *, pg_sleep(1.1) from messages where 0 <= key and key < 3;"); - std::string query("select *, pg_sleep(0.5) from messages where 0 <= key and key < 3;"); spawn(pg::exec(conn, query) | ex::then([&](pg::result res, auto&&...) noexcept { for (int i{}, n{PQntuples(res.get())}; i < n; ++i) { for (int j{}, m{PQnfields(res.get())}; j < m; ++j) { @@ -233,9 +229,7 @@ int main() { }) | ex::upon_error([](pg::error error) noexcept { std::cout << "query error: " << error << "\n"; })); - sync_run(loop, - ex::when_all(scope.join(), io.async_run()) - | ex::upon_stopped([]() noexcept {})); + sync_run(loop, ex::when_all(scope.join(), io.async_run()) | ex::upon_stopped([]() noexcept {})); } catch (const pg::error& e) { std::cout << "Error: " << e << '\n'; } diff --git a/include/beman/net/detail/io_context.hpp b/include/beman/net/detail/io_context.hpp index 1fc2a90..fb9f71c 100644 --- a/include/beman/net/detail/io_context.hpp +++ b/include/beman/net/detail/io_context.hpp @@ -96,7 +96,7 @@ class beman::net::io_context { auto listen(::beman::net::detail::socket_id id, int no, ::std::error_code& error) { this->d_context.listen(id, no, error); } - auto get_scheduler() -> scheduler_type { return scheduler_type(&this->d_context); } + auto get_scheduler() noexcept -> scheduler_type { return scheduler_type(&this->d_context); } template struct run_one_state { From 1a57468c8835a91c0aec0da45053ce6fcd7eadf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Mon, 27 Apr 2026 18:51:43 -0400 Subject: [PATCH 13/25] fixed the CMakeLists.txt to conditionally include postgres --- CMakeLists.txt | 4 ++++ examples/CMakeLists.txt | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d05cba1..2e66488 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,10 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(task) +include(CheckLibraryExists) +set(POSTGRESROOT /Library/PostgreSQL/18) +check_library_exists(pq PQsocket ${POSTGRESROOT}/lib HAS_POSTGRES) + add_subdirectory(src/beman/net) #=============================================================================== diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e52e937..5ada40e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -29,12 +29,17 @@ set(EXAMPLES http-server milano http-munich - postgres - populate-postgres server taps task ) + +if(${HAS_POSTGRES}) +list(APPEND EXAMPLES + postgres + populate-postgres +) +endif() set(xEXAMPLES taps) set(xEXAMPLES postgres populate-postgres) @@ -45,9 +50,11 @@ foreach(EXAMPLE ${EXAMPLES}) # XXX target_compile_definitions( ${EXAMPLE_TARGET} PRIVATE BEMAN_NET_USE_URING) endif() target_sources(${EXAMPLE_TARGET} PRIVATE ${EXAMPLE}.cpp) - target_include_directories(${EXAMPLE_TARGET} PRIVATE /Library/PostgreSQL/18/include) - target_link_directories(${EXAMPLE_TARGET} PRIVATE /Library/PostgreSQL/18/lib) - target_link_libraries(${EXAMPLE_TARGET} PRIVATE pq) + if(${HAS_POSTGRES}) + target_include_directories(${EXAMPLE_TARGET} PRIVATE ${POSTGRESROOT}/include) + target_link_directories(${EXAMPLE_TARGET} PRIVATE ${POSTGRESROOT}/lib) + target_link_libraries(${EXAMPLE_TARGET} PRIVATE pq) + endif() target_link_libraries(${EXAMPLE_TARGET} PRIVATE beman::net_headers) target_link_libraries(${EXAMPLE_TARGET} PRIVATE beman::task_headers) endforeach() From a12f7f3f7c17d23654c41a735085e614e1b59a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Mon, 27 Apr 2026 22:20:52 -0400 Subject: [PATCH 14/25] some clean-up and better prepration for the postgres presentation --- examples/CMakeLists.txt | 1 - examples/postgres-talk.cpp | 50 ++++++++++++++ examples/postgres.cpp | 122 +++++++-------------------------- examples/print_completions.hpp | 52 ++++++++++++++ examples/sync_run.hpp | 28 ++++++++ 5 files changed, 156 insertions(+), 97 deletions(-) create mode 100644 examples/postgres-talk.cpp create mode 100644 examples/print_completions.hpp create mode 100644 examples/sync_run.hpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 5ada40e..f1a0b40 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -41,7 +41,6 @@ list(APPEND EXAMPLES ) endif() set(xEXAMPLES taps) -set(xEXAMPLES postgres populate-postgres) foreach(EXAMPLE ${EXAMPLES}) set(EXAMPLE_TARGET beman.net.examples.${EXAMPLE}) diff --git a/examples/postgres-talk.cpp b/examples/postgres-talk.cpp new file mode 100644 index 0000000..cfb20cb --- /dev/null +++ b/examples/postgres-talk.cpp @@ -0,0 +1,50 @@ +// examples/postgres-talk.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// examples/http-server.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include "demo_algorithm.hpp" +#include +#include +#include + +namespace ex = beman::execution; +namespace net = beman::net; +using namespace std::chrono_literals; + +// PQconnectdb(const char* connstr) -> PGconn* +// PQexec(const PGconn *conn, const char* query) -> PGresult* - query the database +// PQsendQuery(const PGconn *conn, const char* query) - send a query +// PQconsumeInput(const PGconn *conn) - consume available input, clear socket stat +// PQgetResult(const PGconn *conn) -> PGresult* - get result, return nullptr if no more results, or would block +// PQsetnonblocking(const PGconn *conn, int arg) - set non-blocking mode to avoid write blocks +// PQsocket(const PGconn *conn) - get socket +// PQflush(const PGconn *conn) - flush output buffer, return 1 if still pending data +// PQisBusy(const PGconn *conn) -> int - PQgetResult() would block +// PQsetSingleRowMode(PGconn *conn) - set single row mode, return 0 on failure +// PQsetChunkedMode(PGconn *conn, int arg) - set chunked mode, return 0 on failure + +namespace { + const std::string connection_string("user=sruser dbname=sruser"); + const std::string query("select *, pg_sleep(0.5) from messages where 0 < key and key < 5;"); + + void print_result(const PGresult* result) { + std::cout << "n=" << PQntuples(result) << ", m=" << PQnfields(result) << "\n"; + for (int i=0, n=PQntuples(result); i < n; ++i) { + for (int j=0, m=PQnfields(result); j < m; ++j == m || std::cout << ", ") { + std::cout << PQgetvalue(result, i, j); + } + std::cout << "\n"; + } + } +} + +// ---------------------------------------------------------------------------- + +auto main() -> int { + std::cout << std::unitbuf << "Postgres Example\n"; +} diff --git a/examples/postgres.cpp b/examples/postgres.cpp index d98ac13..db75adc 100644 --- a/examples/postgres.cpp +++ b/examples/postgres.cpp @@ -43,7 +43,11 @@ struct error { friend std::ostream& operator<<(std::ostream& os, const error& err) { return os << err.msg; } }; -using result = std::unique_ptr; +struct result { + std::unique_ptr res; + explicit result(PGresult* r): res(r) {} + operator PGresult*() const { return this->res.get(); } +}; // PQsetnonblocking(const PGconn *conn, int arg) - set non-blocking mode to avoid write blocks // PQsocket(const PGconn *conn) - get socket @@ -86,7 +90,7 @@ struct exec { co_yield ex::with_error(pg::error(conn)); } } - if (pg::result res{pg::result(PQgetResult(conn))}) { + if (pg::result res{PQgetResult(conn)}) { co_return std::move(res); } else { co_yield ex::with_error(pg::error(conn)); @@ -114,122 +118,48 @@ struct exec { std::string query; }; -inline constexpr double sleep_time = 3.0; } // namespace pg -namespace { -struct sync_run_env { - ex::run_loop& loop; - auto query(const ex::get_scheduler_t&) const noexcept { return this->loop.get_scheduler(); } -}; -struct sync_run_receiver { - ex::run_loop& loop; - using receiver_concept = ex::receiver_t; - - auto get_env() const noexcept { return sync_run_env{this->loop}; } - auto set_value() noexcept { this->loop.finish(); } -}; -auto sync_run(ex::run_loop& loop, ex::sender auto snd) { - auto state{ex::connect(std::move(snd), sync_run_receiver{loop})}; - ex::start(state); - std::cout << "running loop\n"; - loop.run(); - std::cout << "running loop done\n"; -} - -template -struct print_completion_signatures_t; -template -struct print_completion_signatures_t> { - void operator()(std::ostream& os) const { ((os << typeid(Signatures).name() << ','), ...); } -}; -template -inline constexpr print_completion_signatures_t print_completion_signatures{}; - -struct print_completions_t { - template - struct sender { - using sender_concept = ex::sender_t; - template - static consteval auto get_completion_signatures() noexcept { - return ex::get_completion_signatures(); - } - - template - struct state { - using operation_state_concept = ex::operation_state_t; - using state_t = ex::connect_result_t; - using env_t = ex::env_of_t; - - state_t state_; - state(Receiver&& r, Sender&& s) - : state_(ex::connect(std::forward(s), std::forward(r))) {} - auto start() noexcept -> void { - std::cout << "completion_signatures<"; - print_completion_signatures())>(std::cout); - std::cout << ">\n"; - ex::start(this->state_); - } - }; - - std::remove_cvref_t sender; - template - auto connect(Receiver&& r) && { - return state{std::forward(r), std::move(sender)}; - } - }; - - template - auto operator()(Sender&& sndr) const { - return sender{std::forward(sndr)}; - } -}; - -[[maybe_unused]] inline constexpr print_completions_t print_completions{}; - -} // namespace - int main() { std::cout << std::unitbuf << "PostgreSQL example\n"; try { net::io_context io; pg::connection conn(io, PQconnectdb("user=sruser dbname=sruser")); ex::counting_scope scope; - ex::run_loop loop; auto spawn{[&](ex::sender auto s) { - ex::spawn(ex::starts_on(loop.get_scheduler(), std::move(s)), scope.get_token()); + ex::spawn(ex::starts_on(io.get_scheduler(), std::move(s)), scope.get_token()); }}; struct noexcept_env { using error_types = ex::completion_signatures<>; + using scheduler_type = decltype(io.get_scheduler()); }; - spawn(ex::just()); spawn(std::invoke( - [](auto sched) noexcept -> ex::task { + []() noexcept -> ex::task { while (true) { - std::cout << "\rtime=" << std::chrono::system_clock::now() << "\n" << std::flush; - co_await net::resume_after(sched, 1s); + std::cout << "time=" << std::chrono::system_clock::now() << "\n" << std::flush; + co_await net::resume_after(co_await ex::read_env(ex::get_scheduler), 1s); } - }, - io.get_scheduler())); + })); std::string query("select *, pg_sleep(1.1) from messages where 0 <= key and key < 3;"); - spawn(pg::exec(conn, query) | ex::then([&](pg::result res, auto&&...) noexcept { - for (int i{}, n{PQntuples(res.get())}; i < n; ++i) { - for (int j{}, m{PQnfields(res.get())}; j < m; ++j) { - std::cout << PQgetvalue(res.get(), i, j) << ','; - } - std::cout << '\n'; - } - std::cout << "query done\n" << std::flush; - scope.request_stop(); - }) | - ex::upon_error([](pg::error error) noexcept { std::cout << "query error: " << error << "\n"; })); - - sync_run(loop, ex::when_all(scope.join(), io.async_run()) | ex::upon_stopped([]() noexcept {})); + spawn(pg::exec(conn, query) | ex::then([](pg::result res) noexcept { + for (int i{}, n{PQntuples(res)}; i < n; ++i) { + for (int j{}, m{PQnfields(res)}; j < m; ++j) { + std::cout << PQgetvalue(res, i, j) << ','; + } + std::cout << '\n'; + } + std::cout << "query done\n" << std::flush; + }) + | ex::upon_error([](pg::error error) noexcept { std::cout << "query error: " << error << "\n"; }) + | ex::then([&scope]() noexcept { scope.request_stop(); }) + ); + + ex::sync_wait(ex::when_all(scope.join(), io.async_run()) | ex::upon_stopped([]() noexcept {})); } catch (const pg::error& e) { std::cout << "Error: " << e << '\n'; } diff --git a/examples/print_completions.hpp b/examples/print_completions.hpp new file mode 100644 index 0000000..3566c5d --- /dev/null +++ b/examples/print_completions.hpp @@ -0,0 +1,52 @@ +// examples/print_completions.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +template +struct print_completion_signatures_t; +template +struct print_completion_signatures_t> { + void operator()(std::ostream& os) const { ((os << typeid(Signatures).name() << ','), ...); } +}; +template +inline constexpr print_completion_signatures_t print_completion_signatures{}; + +struct print_completions_t { + template + struct sender { + using sender_concept = ex::sender_t; + template + static consteval auto get_completion_signatures() noexcept { + return ex::get_completion_signatures(); + } + + template + struct state { + using operation_state_concept = ex::operation_state_t; + using state_t = ex::connect_result_t; + using env_t = ex::env_of_t; + + state_t state_; + state(Receiver&& r, Sender&& s) + : state_(ex::connect(std::forward(s), std::forward(r))) {} + auto start() noexcept -> void { + std::cout << "completion_signatures<"; + print_completion_signatures())>(std::cout); + std::cout << ">\n"; + ex::start(this->state_); + } + }; + + std::remove_cvref_t sender; + template + auto connect(Receiver&& r) && { + return state{std::forward(r), std::move(sender)}; + } + }; + + template + auto operator()(Sender&& sndr) const { + return sender{std::forward(sndr)}; + } +}; + +[[maybe_unused]] inline constexpr print_completions_t print_completions{}; diff --git a/examples/sync_run.hpp b/examples/sync_run.hpp new file mode 100644 index 0000000..1955ffd --- /dev/null +++ b/examples/sync_run.hpp @@ -0,0 +1,28 @@ +// examples/sync_run.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_EXAMPLES_SYNC_RUN +#define INCLUDED_EXAMPLES_SYNC_RUN + +struct sync_run_env { + ex::run_loop& loop; + auto query(const ex::get_scheduler_t&) const noexcept { return this->loop.get_scheduler(); } +}; +struct sync_run_receiver { + ex::run_loop& loop; + using receiver_concept = ex::receiver_t; + + auto get_env() const noexcept { return sync_run_env{this->loop}; } + auto set_value() noexcept { this->loop.finish(); } +}; +auto sync_run(ex::run_loop& loop, ex::sender auto snd) { + auto state{ex::connect(std::move(snd), sync_run_receiver{loop})}; + ex::start(state); + std::cout << "running loop\n"; + loop.run(); + std::cout << "running loop done\n"; +} + +// ---------------------------------------------------------------------------- + +#endif From 4c80cb8b34278dfdd90b8802bfd2c1e4f744a947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Tue, 28 Apr 2026 00:10:37 -0400 Subject: [PATCH 15/25] minor usability improvement for socket creation --- examples/postgres.cpp | 2 +- include/beman/net/detail/basic_stream_socket.hpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/postgres.cpp b/examples/postgres.cpp index db75adc..c52e277 100644 --- a/examples/postgres.cpp +++ b/examples/postgres.cpp @@ -23,7 +23,7 @@ struct connection { handle_t handle; net::ip::tcp::socket socket; connection(net::io_context& io, PGconn* conn) - : handle(conn), socket(io.get_scheduler().get_context(), io.make_socket(PQsocket(handle.get()))) { + : handle(conn), socket(io, io.make_socket(PQsocket(handle.get()))) { if (PQstatus(conn) != CONNECTION_OK) { throw std::runtime_error(std::string("Connection to database failed: ") + PQerrorMessage(conn)); } diff --git a/include/beman/net/detail/basic_stream_socket.hpp b/include/beman/net/detail/basic_stream_socket.hpp index 550f691..9a35969 100644 --- a/include/beman/net/detail/basic_stream_socket.hpp +++ b/include/beman/net/detail/basic_stream_socket.hpp @@ -29,6 +29,8 @@ class beman::net::basic_stream_socket : public basic_socket { basic_stream_socket& operator=(basic_stream_socket&&) = default; basic_stream_socket(::beman::net::detail::context_base* context, ::beman::net::detail::socket_id id) : basic_socket(context, id) {} + basic_stream_socket(::beman::net::io_context& context, ::beman::net::detail::socket_id id) + : basic_socket(context.get_scheduler().get_context(), id) {} basic_stream_socket(::beman::net::io_context& context, const endpoint_type& endpoint) : beman::net::basic_socket( context.get_scheduler().get_context(), ::std::invoke([p = endpoint.protocol(), &context] { From 5c3aba006a937b8801457557d86cc3df72a9ca3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Fri, 1 May 2026 21:37:36 -0400 Subject: [PATCH 16/25] started adding a simple HTTP server --- examples/CMakeLists.txt | 1 + examples/demo_http.hpp | 128 ++++++++++++++++++ examples/postgres-talk.cpp | 100 ++++++++++++++ .../net/detail/basic_socket_acceptor.hpp | 3 +- .../beman/net/detail/repeat_effect_until.hpp | 26 +++- 5 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 examples/demo_http.hpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index f1a0b40..abfe785 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -41,6 +41,7 @@ list(APPEND EXAMPLES ) endif() set(xEXAMPLES taps) +set(EXAMPLES postgres-talk) foreach(EXAMPLE ${EXAMPLES}) set(EXAMPLE_TARGET beman.net.examples.${EXAMPLE}) diff --git a/examples/demo_http.hpp b/examples/demo_http.hpp new file mode 100644 index 0000000..5e79ce7 --- /dev/null +++ b/examples/demo_http.hpp @@ -0,0 +1,128 @@ +// examples/demo_http.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_EXAMPLES_DEMO_HTTP +#define INCLUDED_EXAMPLES_DEMO_HTTP + +#include +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace demo::http { + namespace ex = ::beman::execution; + namespace net = ::beman::net; + struct async_stream { + using buffer_t = ::std::array; + using buffer_iterator = typename buffer_t::iterator; + + ::beman::net::ip::tcp::socket socket; + buffer_t buffer{}; + buffer_iterator it{buffer.begin()}; + buffer_iterator end{buffer.end()}; + + static constexpr std::size_t length(char) { return 1u; } + bool consume(auto& to, char sentinel) { + auto end{::std::find(this->it, this->end, sentinel)}; + to.insert(to.end(), this->it, end); + this->it = end; + return this->it == this->end; + } + template <::std::size_t Size> + static constexpr std::size_t length(const char (&)[Size]) { return Size - 1u; } + template <::std::size_t Size> + bool consume(auto& to, const char (&sentinel)[Size]) { + auto end{::std::search(this->it, this->end, sentinel, sentinel + Size - 1)}; + if (end == this->end) { + end -= std::min(std::size_t(end - this->it), Size); + to.insert(to.end(), this->it, end); + std::cout << "insert(" << std::string_view(this->it, end) << ") - continue\n"; + this->it = this->buffer.begin(); + this->end = std::move(end, this->end, this->it); + return true; + } + else { + to.insert(to.end(), this->it, end); + std::cout << "insert(" << std::string_view(this->it, end) << ") - done\n"; + this->it = end; + return false; + } + } + auto read(auto& to, const auto& sentinel)->ex::task { + while (this->consume(to, sentinel)) { + this->it = this->buffer.begin(); + std::size_t n{co_await net::async_receive(this->socket, net::buffer(this->buffer))}; + if (n == 0) { + co_return false; + } + std::cout << "received='" << std::string_view(this->it, n) << "'\n"; + this->end = this->it + n; + } + this->it += length(sentinel); + + co_return true; + } + }; + + struct http_client { + ::demo::http::async_stream stream; + public: + http_client(::beman::net::ip::tcp::socket s, auto): stream{std::move(s)} {} + auto request() ->ex::task<> { + std::vector method, url, version; + if (!(co_await stream.read(method, ' '))) { + co_return; + } + std::cout << "read method='" << std::string_view(method) << "'\n"; + + if (!(co_await stream.read(url, ' '))) { + co_return; + } + std::cout << "read url='" << std::string_view(url) << "'\n"; + + if (!(co_await stream.read(version, "\r\n"))) { + co_return; + } + std::cout << "read version='" << std::string_view(version) << "'\n"; + + std::vector header; + while (co_await stream.read(header, "\r\n") && !header.empty()) { + std::cout << "read header line='" << std::string_view(header) << "'\n"; + header.clear(); + } + std::cout << "read HTTP GET request\n"; + + co_return; + } + }; + + struct no_error_env { + using error_types = ::beman::execution::completion_signatures<>; + }; + + template + ::beman::execution::task + http_server(::beman::net::io_context& io, unsigned short port, SenderFactory fun) { + ::beman::net::ip::tcp::endpoint ep(::beman::net::ip::address_v4::any(), port); + ::beman::net::ip::tcp::acceptor server(io, ep); + while (true) { + auto[client, addr] = co_await ::beman::net::async_accept(server); + std::cout << "connection from " << addr << "\n"; + fun(demo::http::http_client(std::move(client), std::move(addr))); + } + } +} + +namespace demo { + using http::http_client; + using http::http_server; +} + +// ---------------------------------------------------------------------------- + +#endif diff --git a/examples/postgres-talk.cpp b/examples/postgres-talk.cpp index cfb20cb..7ce59b3 100644 --- a/examples/postgres-talk.cpp +++ b/examples/postgres-talk.cpp @@ -5,9 +5,11 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include +#include #include #include #include "demo_algorithm.hpp" +#include "demo_http.hpp" #include #include #include @@ -43,8 +45,106 @@ namespace { } } +namespace pg { +struct connection { + std::unique_ptr conn; + net::ip::tcp::socket socket; + connection(net::io_context& io, PGconn* c): + conn(c? c: throw std::runtime_error("connection failed")), + socket(io, io.make_socket(PQsocket(conn.get()))) {} + operator PGconn*() const { return conn.get(); } +}; + +struct error { + std::string msg; +}; + +struct env { + using error_types = ex::completion_signatures; +}; + +struct result { + std::unique_ptr res; + result(PGresult* r): res(r) {} + operator PGresult*() const { return res.get(); } +}; + +auto exec(pg::connection& conn, const char* query) { + return + ex::just() + | ex::then([&conn, query] noexcept { PQsendQuery(conn, query); }) + | net::repeat_effect_until( + net::async_poll(conn.socket, net::event_type::out) + | ex::upon_error([](auto&&){}) + | ex::then([](auto&&...) noexcept {}), + [&conn] noexcept { return not PQflush(conn); } + ) + | net::repeat_effect_until( + net::async_poll(conn.socket, net::event_type::in) + | ex::upon_error([](auto&&){}) + | ex::then([&conn](auto&&...) noexcept { PQconsumeInput(conn); }), + [&conn] noexcept { return !PQisBusy(conn); } + ) + | ex::then([&conn] noexcept { return pg::result(PQgetResult(conn)); }) + ; +} +ex::task exec1(pg::connection& conn, const char* query) { + PQsendQuery(conn, query); + while (PQflush(conn)) { + co_await net::async_poll(conn.socket, net::event_type::out); + } + while (PQisBusy(conn)) { + co_await net::async_poll(conn.socket, net::event_type::in); + if (!PQconsumeInput(conn)) { + co_yield ex::with_error(pg::error(PQerrorMessage(conn))); + } + } + pg::result res(PQgetResult(conn)); + if (!res) { + co_yield ex::with_error(pg::error(PQerrorMessage(conn))); + } + co_return std::move(res); +} +} + // ---------------------------------------------------------------------------- auto main() -> int { std::cout << std::unitbuf << "Postgres Example\n"; + net::io_context io; + pg::connection conn(io, PQconnectdb(connection_string.c_str())); + ex::counting_scope scope; + auto spawn{[&](ex::sender auto s){ + ex::spawn(ex::starts_on(io.get_scheduler(), std::move(s)), scope.get_token()); + }}; + + spawn(demo::http_server(io, 12345, [&spawn](auto client) noexcept { + std::cout << "got a client\n"; + spawn([](auto client)->ex::task{ + std::cout << "reading request\n"; + co_await client.request(); + std::cout << "client done\n"; + }(std::move(client))); + })); + + struct io_env { + using scheduler_type = decltype(io.get_scheduler()); + using error_types = ex::completion_signatures<>; + }; + + spawn([]()->ex::task { + while (true) { + std::cout << "time=" << std::chrono::system_clock::now() << "\n"; + co_await net::resume_after(co_await ex::read_env(ex::get_scheduler), 100s); + } + }()); + if (false) { + spawn([](auto& conn, auto& ) noexcept ->ex::task { + pg::result res{co_await pg::exec(conn, query.c_str())}; + print_result(res); + //scope.request_stop(); + }(conn, scope)); + } + + ex::sync_wait(ex::when_all(io.async_run(), scope.join())); } diff --git a/include/beman/net/detail/basic_socket_acceptor.hpp b/include/beman/net/detail/basic_socket_acceptor.hpp index 2a6fb1c..33cc69d 100644 --- a/include/beman/net/detail/basic_socket_acceptor.hpp +++ b/include/beman/net/detail/basic_socket_acceptor.hpp @@ -59,8 +59,9 @@ class beman::net::basic_socket_acceptor : public ::beman::net::socket_base { } basic_socket_acceptor(::beman::net::io_context&, const protocol_type&, const native_handle_type&); basic_socket_acceptor(const basic_socket_acceptor&) = delete; - basic_socket_acceptor(basic_socket_acceptor&& other) + basic_socket_acceptor(basic_socket_acceptor&& other) noexcept : ::beman::net::socket_base(), + d_context(other.d_context), d_protocol(other.d_protocol), d_id(::std::exchange(other.d_id, ::beman::net::detail::socket_id::invalid)) {} template diff --git a/include/beman/net/detail/repeat_effect_until.hpp b/include/beman/net/detail/repeat_effect_until.hpp index fae5719..99b58d8 100644 --- a/include/beman/net/detail/repeat_effect_until.hpp +++ b/include/beman/net/detail/repeat_effect_until.hpp @@ -12,13 +12,37 @@ // ---------------------------------------------------------------------------- namespace beman::net::detail { -struct repeat_effect_until_t : beman::execution::sender_adaptor_closure { +struct repeat_effect_until_t + : beman::execution::sender_adaptor_closure +{ template auto operator()(Upstream&& upstream, Body&& body, Predicate&& predicate) const { return sender, std::remove_cvref_t, std::remove_cvref_t>{ std::forward(upstream), std::forward(body), std::forward(predicate)}; } + template + struct adaptor + : beman::execution::sender_adaptor_closure> + { + std::remove_cvref_t body; + std::remove_cvref_t predicate; + template + adaptor(B&& b, P&& p): body(std::forward(b)), predicate(std::forward

(p)) {} + template + auto operator()(Upstream&& upstream) && { + return repeat_effect_until_t{}( + std::forward(upstream), + std::move(this->body), + std::move(this->predicate) + ); + } + }; + template + auto operator()(Body&& body, Predicate&& predicate) const { + return adaptor{std::forward(body), std::forward(predicate)}; + } + template Date: Sat, 2 May 2026 10:43:32 -0400 Subject: [PATCH 17/25] added an async in-memory stream --- examples/CMakeLists.txt | 1 + examples/demo_stream.hpp | 189 +++++++++++++++++++++++++++++++++++++ examples/memory-stream.cpp | 40 ++++++++ 3 files changed, 230 insertions(+) create mode 100644 examples/demo_stream.hpp create mode 100644 examples/memory-stream.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index abfe785..fd4cf67 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -42,6 +42,7 @@ list(APPEND EXAMPLES endif() set(xEXAMPLES taps) set(EXAMPLES postgres-talk) +set(EXAMPLES memory-stream) foreach(EXAMPLE ${EXAMPLES}) set(EXAMPLE_TARGET beman.net.examples.${EXAMPLE}) diff --git a/examples/demo_stream.hpp b/examples/demo_stream.hpp new file mode 100644 index 0000000..a5a0fe2 --- /dev/null +++ b/examples/demo_stream.hpp @@ -0,0 +1,189 @@ +// examples/demo_stream.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_EXAMPLES_DEMO_STREAM +#define INCLUDED_EXAMPLES_DEMO_STREAM + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace demo::stream { + namespace ex = ::beman::execution; + namespace net = ::beman::net; + + struct tcp { + ::beman::net::ip::tcp::socket socket; + template + auto receive(Buffer buffer) { + return net::async_receive(this->socket, buffer); + } + }; + + struct memory_base { + virtual ~memory_base() = default; + virtual auto add_receive_data(std::string_view data) -> std::size_t = 0; + }; + struct context { + using update_fun = std::function; + std::unordered_map streams; + std::vector> updates; + std::size_t next_index{}; + void next() { + auto& update = updates[next_index]; + memory_base* stream = streams[update.first]; + if (stream && update.second(*stream)) { + this->updates.erase(this->updates.begin() + next_index); + } + else if (this->updates.size() == ++this->next_index) { + this->next_index = 0u; + } + } + void add(std::string name, update_fun fun) { + this->updates.emplace_back(std::move(name), std::move(fun)); + } + auto async_run_one() { + return ex::just() | ex::then([this]{ this->next(); }); + } + auto async_run() { + return ex::just() | net::repeat_effect_until( + ex::read_env(ex::get_scheduler) + | ex::let_value([this](auto sched){ + return ex::schedule(sched) + | ex::let_value([this]{ return this->async_run_one(); }); + }), + [this]{ return this->updates.empty(); } + ); + } + }; + struct memory { + template + struct state + : memory_base + { + using operation_state_concept = ex::operation_state_t; + Receiver receiver; + Buffer buffer; + memory* self; + + template + state(R&& r, B&& b, memory* s) + : receiver(std::forward(r)) + , buffer(std::forward(b)) + , self(s) { + } + auto start() & noexcept { + this->self->receive_state = this; + this->self->ctxt->streams[this->self->name] = this; + } + auto add_receive_data(std::string_view data) -> std::size_t override { + const auto& vec{this->buffer.data()}; + std::size_t n{std::min(data.size(), vec[0].iov_len)}; + std::copy(data.begin(), data.begin() + n, static_cast(vec[0].iov_base)); + this->self->ctxt->streams[this->self->name] = nullptr; + ex::set_value(std::move(this->receiver), n); + return n; + } + }; + template + struct receive_sender { + using sender_concept = ex::sender_t; + template + static consteval auto get_completion_signatures() { + return ex::completion_signatures{}; + } + memory* self; + Buffer buffer; + + template + state, Buffer> connect(Receiver&& receiver) && { + static_assert(ex::operation_state, Buffer>>); + return {std::forward(receiver), std::move(this->buffer), this->self }; + } + }; + template + auto receive(Buffer buffer) { + static_assert(ex::sender>); + return receive_sender{this, std::move(buffer)}; + } + + context* ctxt; + std::string name; + memory_base* receive_state{}; + memory(context& c, std::string n): ctxt(&c), name(std::move(n)) { + } + }; + + template + struct basic_buffered { + using buffer_t = ::std::array; + using buffer_iterator = typename buffer_t::iterator; + + Socket socket; + buffer_t buffer{}; + buffer_iterator it{buffer.begin()}; + buffer_iterator end{buffer.end()}; + + static constexpr std::size_t length(char) { return 1u; } + bool consume(auto& to, char sentinel) { + auto end{::std::find(this->it, this->end, sentinel)}; + to.insert(to.end(), this->it, end); + this->it = end; + return this->it == this->end; + } + template <::std::size_t Size> + static constexpr std::size_t length(const char (&)[Size]) { return Size - 1u; } + template <::std::size_t Size> + bool consume(auto& to, const char (&sentinel)[Size]) { + auto end{::std::search(this->it, this->end, sentinel, sentinel + Size - 1)}; + if (end == this->end) { + end -= std::min(std::size_t(end - this->it), Size); + to.insert(to.end(), this->it, end); + this->it = this->buffer.begin(); + this->end = std::move(end, this->end, this->it); + return true; + } + else { + to.insert(to.end(), this->it, end); + this->it = end; + return false; + } + } + auto read(auto& to, const auto& sentinel)->ex::task { + while (this->consume(to, sentinel)) { + this->it = this->buffer.begin(); + std::size_t n{co_await net::async_receive(this->socket, net::buffer(this->buffer))}; + if (n == 0) { + co_return false; + } + this->end = this->it + n; + } + this->it += length(sentinel); + + co_return true; + } + }; +} + +namespace demo { + using demo::stream::basic_buffered; + using tcp_stream = demo::stream::basic_buffered; + using mem_stream = demo::stream::basic_buffered; + using demo::stream::context; + using demo::stream::memory; + using demo::stream::memory_base; +} + +// ---------------------------------------------------------------------------- + +#endif diff --git a/examples/memory-stream.cpp b/examples/memory-stream.cpp new file mode 100644 index 0000000..5c83d5f --- /dev/null +++ b/examples/memory-stream.cpp @@ -0,0 +1,40 @@ +// examples/memory-stream.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "demo_stream.hpp" +#include +#include +#include +#include +#include "demo_algorithm.hpp" +#include "demo_http.hpp" +#include +#include +#include + +namespace ex = ::beman::execution; +namespace net = ::beman::net; + +// ---------------------------------------------------------------------------- + +int main() { + std::cout << std::unitbuf; + demo::context context; + context.add("foo", [data=std::string_view("hello, world!")](demo::memory_base& s) mutable { + std::size_t n{s.add_receive_data(data.substr(0, std::min(std::size_t(2), data.size())))}; + if (n == 0u) { + return true; + } + data = data.substr(n); + return false; + }); + auto reader{[](demo::context& context)->ex::task<> { + demo::memory mem(context, "foo"); + std::array buffer; + while (std::size_t n = co_await mem.receive(net::buffer(buffer))) { + std::cout << "stream received=" << std::string_view(buffer.begin(), n) << "\n"; + } + }(context)}; + + ex::sync_wait(ex::when_all(context.async_run(), std::move(reader))); +} From ae1058e0bbd674868d844e8b75006b6b27d307a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sat, 2 May 2026 12:27:42 -0600 Subject: [PATCH 18/25] added an in-memory stream --- examples/demo_http.hpp | 59 ++++---------------------------------- examples/demo_stream.hpp | 38 ++++++++++++------------ examples/memory-stream.cpp | 24 +++++++--------- 3 files changed, 35 insertions(+), 86 deletions(-) diff --git a/examples/demo_http.hpp b/examples/demo_http.hpp index 5e79ce7..8f1fb63 100644 --- a/examples/demo_http.hpp +++ b/examples/demo_http.hpp @@ -7,6 +7,7 @@ #include #include #include +#include "demo_stream.hpp" #include #include #include @@ -17,62 +18,12 @@ namespace demo::http { namespace ex = ::beman::execution; namespace net = ::beman::net; - struct async_stream { - using buffer_t = ::std::array; - using buffer_iterator = typename buffer_t::iterator; - - ::beman::net::ip::tcp::socket socket; - buffer_t buffer{}; - buffer_iterator it{buffer.begin()}; - buffer_iterator end{buffer.end()}; - - static constexpr std::size_t length(char) { return 1u; } - bool consume(auto& to, char sentinel) { - auto end{::std::find(this->it, this->end, sentinel)}; - to.insert(to.end(), this->it, end); - this->it = end; - return this->it == this->end; - } - template <::std::size_t Size> - static constexpr std::size_t length(const char (&)[Size]) { return Size - 1u; } - template <::std::size_t Size> - bool consume(auto& to, const char (&sentinel)[Size]) { - auto end{::std::search(this->it, this->end, sentinel, sentinel + Size - 1)}; - if (end == this->end) { - end -= std::min(std::size_t(end - this->it), Size); - to.insert(to.end(), this->it, end); - std::cout << "insert(" << std::string_view(this->it, end) << ") - continue\n"; - this->it = this->buffer.begin(); - this->end = std::move(end, this->end, this->it); - return true; - } - else { - to.insert(to.end(), this->it, end); - std::cout << "insert(" << std::string_view(this->it, end) << ") - done\n"; - this->it = end; - return false; - } - } - auto read(auto& to, const auto& sentinel)->ex::task { - while (this->consume(to, sentinel)) { - this->it = this->buffer.begin(); - std::size_t n{co_await net::async_receive(this->socket, net::buffer(this->buffer))}; - if (n == 0) { - co_return false; - } - std::cout << "received='" << std::string_view(this->it, n) << "'\n"; - this->end = this->it + n; - } - this->it += length(sentinel); - - co_return true; - } - }; + template struct http_client { - ::demo::http::async_stream stream; + Stream stream; public: - http_client(::beman::net::ip::tcp::socket s, auto): stream{std::move(s)} {} + http_client(Stream s): stream{std::move(s)} {} auto request() ->ex::task<> { std::vector method, url, version; if (!(co_await stream.read(method, ' '))) { @@ -113,7 +64,7 @@ namespace demo::http { while (true) { auto[client, addr] = co_await ::beman::net::async_accept(server); std::cout << "connection from " << addr << "\n"; - fun(demo::http::http_client(std::move(client), std::move(addr))); + fun(demo::http::http_client(demo::tcp_stream(std::move(client)))); } } } diff --git a/examples/demo_stream.hpp b/examples/demo_stream.hpp index a5a0fe2..4a379c9 100644 --- a/examples/demo_stream.hpp +++ b/examples/demo_stream.hpp @@ -42,7 +42,7 @@ namespace demo::stream { void next() { auto& update = updates[next_index]; memory_base* stream = streams[update.first]; - if (stream && update.second(*stream)) { + if (stream == nullptr || update.second(*stream)) { this->updates.erase(this->updates.begin() + next_index); } else if (this->updates.size() == ++this->next_index) { @@ -124,49 +124,49 @@ namespace demo::stream { } }; - template + template struct basic_buffered { using buffer_t = ::std::array; using buffer_iterator = typename buffer_t::iterator; - Socket socket; + Stream stream; buffer_t buffer{}; buffer_iterator it{buffer.begin()}; - buffer_iterator end{buffer.end()}; + buffer_iterator end{buffer.begin()}; + template + basic_buffered(Args&&... args): stream(std::forward(args)...) {} static constexpr std::size_t length(char) { return 1u; } bool consume(auto& to, char sentinel) { auto end{::std::find(this->it, this->end, sentinel)}; to.insert(to.end(), this->it, end); this->it = end; - return this->it == this->end; + return end == this->end; } template <::std::size_t Size> static constexpr std::size_t length(const char (&)[Size]) { return Size - 1u; } template <::std::size_t Size> bool consume(auto& to, const char (&sentinel)[Size]) { auto end{::std::search(this->it, this->end, sentinel, sentinel + Size - 1)}; - if (end == this->end) { - end -= std::min(std::size_t(end - this->it), Size); - to.insert(to.end(), this->it, end); - this->it = this->buffer.begin(); - this->end = std::move(end, this->end, this->it); - return true; - } - else { - to.insert(to.end(), this->it, end); - this->it = end; - return false; + bool rc(end == this->end); + if (rc) { + end -= std::min(std::size_t(std::distance(this->it, end)), Size); } + to.insert(to.end(), this->it, end); + this->it = end; + return rc; } auto read(auto& to, const auto& sentinel)->ex::task { while (this->consume(to, sentinel)) { - this->it = this->buffer.begin(); - std::size_t n{co_await net::async_receive(this->socket, net::buffer(this->buffer))}; + if (std::distance(this->end, this->buffer.end()) < this->buffer.size() / 2) { + this->end = std::move(this->it, this->end, this->buffer.begin()); + this->it = this->buffer.begin(); + } + std::size_t n{co_await this->stream.receive(net::buffer(std::string_view(this->end, this->buffer.end())))}; if (n == 0) { co_return false; } - this->end = this->it + n; + this->end += n; } this->it += length(sentinel); diff --git a/examples/memory-stream.cpp b/examples/memory-stream.cpp index 5c83d5f..1eef4de 100644 --- a/examples/memory-stream.cpp +++ b/examples/memory-stream.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "demo_stream.hpp" +#include "demo_http.hpp" #include #include #include @@ -20,21 +21,18 @@ namespace net = ::beman::net; int main() { std::cout << std::unitbuf; demo::context context; - context.add("foo", [data=std::string_view("hello, world!")](demo::memory_base& s) mutable { - std::size_t n{s.add_receive_data(data.substr(0, std::min(std::size_t(2), data.size())))}; - if (n == 0u) { - return true; + std::string request("GET /some/url HTTP/1.1\r\nHost: localhost:12345\r\nUser-Agent: memory/0.0\r\nAccept: */*\r\n\r\n"); + context.add("foo", [data=std::string_view(request)](demo::memory_base& s) mutable { + if (std::size_t n{s.add_receive_data(data.substr(0, std::min(std::size_t(2), data.size())))}) { + data = data.substr(n); + return false; } - data = data.substr(n); - return false; + return true; }); - auto reader{[](demo::context& context)->ex::task<> { - demo::memory mem(context, "foo"); - std::array buffer; - while (std::size_t n = co_await mem.receive(net::buffer(buffer))) { - std::cout << "stream received=" << std::string_view(buffer.begin(), n) << "\n"; - } - }(context)}; + + auto reader{[](auto client)->ex::task<> { + co_await client.request(); + }(demo::http_client(demo::mem_stream(context, "foo")))}; ex::sync_wait(ex::when_all(context.async_run(), std::move(reader))); } From e8ce047ecec568652be5880e75f15417477a3ef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 3 May 2026 20:59:39 -0600 Subject: [PATCH 19/25] a bit more progress on the HTTP support --- examples/demo_http.hpp | 8 ++++++++ examples/demo_stream.hpp | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/demo_http.hpp b/examples/demo_http.hpp index 8f1fb63..28bbefd 100644 --- a/examples/demo_http.hpp +++ b/examples/demo_http.hpp @@ -12,6 +12,7 @@ #include #include #include +#include // ---------------------------------------------------------------------------- @@ -19,6 +20,13 @@ namespace demo::http { namespace ex = ::beman::execution; namespace net = ::beman::net; + struct request { + std::string method; + std::string url; + std::string version; + std::vector headers; + }; + template struct http_client { Stream stream; diff --git a/examples/demo_stream.hpp b/examples/demo_stream.hpp index 4a379c9..4497394 100644 --- a/examples/demo_stream.hpp +++ b/examples/demo_stream.hpp @@ -158,7 +158,7 @@ namespace demo::stream { } auto read(auto& to, const auto& sentinel)->ex::task { while (this->consume(to, sentinel)) { - if (std::distance(this->end, this->buffer.end()) < this->buffer.size() / 2) { + if (std::size_t(std::distance(this->end, this->buffer.end())) < this->buffer.size() / 2) { this->end = std::move(this->it, this->end, this->buffer.begin()); this->it = this->buffer.begin(); } From 1ffb393b50c802b574d9ee55dbcfc2385f04a42a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 3 May 2026 21:53:06 -0600 Subject: [PATCH 20/25] fixed noexcept of repeat_effect_until::connect --- examples/CMakeLists.txt | 2 +- examples/postgres-talk.cpp | 31 ++++++++++++++----- .../beman/net/detail/repeat_effect_until.hpp | 11 ++++--- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index fd4cf67..64fee27 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -41,8 +41,8 @@ list(APPEND EXAMPLES ) endif() set(xEXAMPLES taps) -set(EXAMPLES postgres-talk) set(EXAMPLES memory-stream) +set(EXAMPLES postgres-talk) foreach(EXAMPLE ${EXAMPLES}) set(EXAMPLE_TARGET beman.net.examples.${EXAMPLE}) diff --git a/examples/postgres-talk.cpp b/examples/postgres-talk.cpp index 7ce59b3..19b40da 100644 --- a/examples/postgres-talk.cpp +++ b/examples/postgres-talk.cpp @@ -34,7 +34,7 @@ namespace { const std::string connection_string("user=sruser dbname=sruser"); const std::string query("select *, pg_sleep(0.5) from messages where 0 < key and key < 5;"); - void print_result(const PGresult* result) { + inline constexpr auto print_result{[](const PGresult* result) noexcept { std::cout << "n=" << PQntuples(result) << ", m=" << PQnfields(result) << "\n"; for (int i=0, n=PQntuples(result); i < n; ++i) { for (int j=0, m=PQnfields(result); j < m; ++j == m || std::cout << ", ") { @@ -42,7 +42,7 @@ namespace { } std::cout << "\n"; } - } + }}; } namespace pg { @@ -57,6 +57,9 @@ struct connection { struct error { std::string msg; + friend std::ostream& operator<< (std::ostream& out, const error& e) { + return out << e.msg; + } }; struct env { @@ -66,29 +69,29 @@ struct env { struct result { std::unique_ptr res; result(PGresult* r): res(r) {} - operator PGresult*() const { return res.get(); } + operator PGresult*() const noexcept { return res.get(); } }; -auto exec(pg::connection& conn, const char* query) { +auto exec2(pg::connection& conn, const char* query) { return ex::just() | ex::then([&conn, query] noexcept { PQsendQuery(conn, query); }) | net::repeat_effect_until( net::async_poll(conn.socket, net::event_type::out) - | ex::upon_error([](auto&&){}) + | ex::upon_error([](auto&&) noexcept {}) | ex::then([](auto&&...) noexcept {}), [&conn] noexcept { return not PQflush(conn); } ) | net::repeat_effect_until( net::async_poll(conn.socket, net::event_type::in) - | ex::upon_error([](auto&&){}) + | ex::upon_error([](auto&&) noexcept {}) | ex::then([&conn](auto&&...) noexcept { PQconsumeInput(conn); }), [&conn] noexcept { return !PQisBusy(conn); } ) | ex::then([&conn] noexcept { return pg::result(PQgetResult(conn)); }) ; } -ex::task exec1(pg::connection& conn, const char* query) { +ex::task exec(pg::connection& conn, const char* query) { PQsendQuery(conn, query); while (PQflush(conn)) { co_await net::async_poll(conn.socket, net::event_type::out); @@ -135,7 +138,7 @@ auto main() -> int { spawn([]()->ex::task { while (true) { std::cout << "time=" << std::chrono::system_clock::now() << "\n"; - co_await net::resume_after(co_await ex::read_env(ex::get_scheduler), 100s); + co_await net::resume_after(co_await ex::read_env(ex::get_scheduler), 1s); } }()); if (false) { @@ -145,6 +148,18 @@ auto main() -> int { //scope.request_stop(); }(conn, scope)); } + else { + spawn( + pg::exec2(conn, query.c_str()) + //ex::just(pg::result(nullptr)) + //| ex::then([](auto) noexcept {}) + | ex::then(print_result) + //| ex::upon_error([](auto) noexcept {}) + | ex::upon_error([](pg::error e) noexcept { + std::cout << "database error=" << e << "\n"; + }) + ); + } ex::sync_wait(ex::when_all(io.async_run(), scope.join())); } diff --git a/include/beman/net/detail/repeat_effect_until.hpp b/include/beman/net/detail/repeat_effect_until.hpp index 99b58d8..07556de 100644 --- a/include/beman/net/detail/repeat_effect_until.hpp +++ b/include/beman/net/detail/repeat_effect_until.hpp @@ -98,10 +98,11 @@ struct repeat_effect_until_t struct sender { using sender_concept = beman::execution::sender_t; using completion_signatures = - beman::execution::completion_signatures; + beman::execution::completion_signatures< + //beman::execution::set_stopped(), + //-dk:TODO add error types of upstream and body + //-dk:TODO add stopped only if upstream or body can be stopped + beman::execution::set_value_t()>; template static constexpr auto get_completion_signatures() -> completion_signatures { return {}; @@ -112,7 +113,7 @@ struct repeat_effect_until_t Predicate predicate; template - auto connect(Receiver&& receiver) { + auto connect(Receiver&& receiver) noexcept(std::is_nothrow_constructible_v, Receiver>) { return state>{std::move(this->upstream), std::move(this->body), std::move(this->predicate), From 56bf5643368086bfa9616422f5f1ddd1a4c3786b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 3 May 2026 22:52:46 -0600 Subject: [PATCH 21/25] improved the completion signature of repeat_effect_until --- include/beman/net/detail/execution.hpp | 1 + include/beman/net/detail/io_context.hpp | 4 ++ .../detail/merge_completion_signatures.hpp | 58 +++++++++++++++++++ .../beman/net/detail/repeat_effect_until.hpp | 23 +++++--- 4 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 include/beman/net/detail/merge_completion_signatures.hpp diff --git a/include/beman/net/detail/execution.hpp b/include/beman/net/detail/execution.hpp index 64d67e8..5d9b447 100644 --- a/include/beman/net/detail/execution.hpp +++ b/include/beman/net/detail/execution.hpp @@ -27,6 +27,7 @@ using ::beman::execution::detail::decayed_tuple; using ::beman::execution::env; using ::beman::execution::env_of_t; using ::beman::execution::error_types_of_t; +using ::beman::execution::sends_stopped; using ::beman::execution::get_env; using ::beman::execution::value_types_of_t; diff --git a/include/beman/net/detail/io_context.hpp b/include/beman/net/detail/io_context.hpp index fb9f71c..422b0ac 100644 --- a/include/beman/net/detail/io_context.hpp +++ b/include/beman/net/detail/io_context.hpp @@ -118,6 +118,10 @@ class beman::net::io_context { using completion_signatures = ::beman::execution::completion_signatures<::beman::execution::set_value_t(std::size_t), ::beman::execution::set_stopped_t()>; + template + static consteval auto get_completion_signatures() { + return completion_signatures{}; + } beman::net::io_context* _context; template diff --git a/include/beman/net/detail/merge_completion_signatures.hpp b/include/beman/net/detail/merge_completion_signatures.hpp new file mode 100644 index 0000000..7a9b73d --- /dev/null +++ b/include/beman/net/detail/merge_completion_signatures.hpp @@ -0,0 +1,58 @@ +// include/beman/net/detail/merge_completion_signatures.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_NET_DETAIL_MERGE_COMPLETION_SIGNATURES +#define INCLUDED_INCLUDE_BEMAN_NET_DETAIL_MERGE_COMPLETION_SIGNATURES + +// ---------------------------------------------------------------------------- + +namespace beman::net::detail { + template struct merge_completion_signatures_unique; + template + struct merge_completion_signatures_unique> { + using type = S0; + }; + template + struct merge_completion_signatures_unique< + ::beman::execution::completion_signatures, + ::beman::execution::completion_signatures + > { + using type = std::conditional_t, + ::beman::execution::completion_signatures + >; + }; + template + struct merge_completion_signatures_unique< + S0, + ::beman::execution::completion_signatures + > { + using type = typename merge_completion_signatures_unique< + typename merge_completion_signatures_unique>::type, + ::beman::execution::completion_signatures + >::type; + }; + + template struct merge_completion_signatures_helper; + template + struct merge_completion_signatures_helper + { + using type = typename merge_completion_signatures_unique::type; + }; + template + struct merge_completion_signatures_helper { + using type = typename merge_completion_signatures_helper< + typename merge_completion_signatures_helper::type, + S2, + Sigs...>::type; + }; + + template + inline consteval auto merge_completion_signatures() { + return typename merge_completion_signatures_helper::type(); + } +} + +// ---------------------------------------------------------------------------- + +#endif diff --git a/include/beman/net/detail/repeat_effect_until.hpp b/include/beman/net/detail/repeat_effect_until.hpp index 07556de..e87f5bc 100644 --- a/include/beman/net/detail/repeat_effect_until.hpp +++ b/include/beman/net/detail/repeat_effect_until.hpp @@ -5,6 +5,7 @@ #define INCLUDED_INCLUDE_BEMAN_NET_DETAIL_REPEAT_EFFECT_UNTIL #include +#include #include #include #include @@ -98,14 +99,22 @@ struct repeat_effect_until_t struct sender { using sender_concept = beman::execution::sender_t; using completion_signatures = - beman::execution::completion_signatures< - //beman::execution::set_stopped(), - //-dk:TODO add error types of upstream and body - //-dk:TODO add stopped only if upstream or body can be stopped - beman::execution::set_value_t()>; + beman::execution::completion_signatures; template - static constexpr auto get_completion_signatures() -> completion_signatures { - return {}; + static consteval auto get_completion_signatures() { + return net::detail::merge_completion_signatures< + completion_signatures, + ex::error_types_of_t, ex::completion_signatures>, + ex::error_types_of_t, ex::completion_signatures>, + std::conditional_t || ex::sends_stopped, + ex::completion_signatures, + ex::completion_signatures<> + >, + std::conditional_t()()), + ex::completion_signatures<>, + ex::completion_signatures + > + >(); } Upstream upstream; From e6e6db2502bc223b1d7794f8d31b039102fb94f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Mon, 4 May 2026 12:09:58 -0600 Subject: [PATCH 22/25] fixed when_any version to actually stop --- examples/demo_algorithm.hpp | 65 ++++++++++++++----- examples/postgres-talk.cpp | 51 +++++++++------ include/beman/net/detail/execution.hpp | 1 + include/beman/net/detail/io_context.hpp | 6 +- .../beman/net/detail/repeat_effect_until.hpp | 2 +- 5 files changed, 84 insertions(+), 41 deletions(-) diff --git a/examples/demo_algorithm.hpp b/examples/demo_algorithm.hpp index 95369ef..de5c7d5 100644 --- a/examples/demo_algorithm.hpp +++ b/examples/demo_algorithm.hpp @@ -141,6 +141,8 @@ struct when_any_t { template struct env; template + struct state_env_base; + template struct state_base; template struct state_value; @@ -183,7 +185,7 @@ struct demo::into_error_t::receiver { template struct demo::into_error_t::sender { using sender_concept = ex::sender_t; - template + template static consteval auto get_completion_signatures() { // static_assert(sizeof...(Env) <= 1u); if constexpr (sizeof...(Env) <= 1) @@ -238,16 +240,32 @@ inline auto demo::into_expected_t::operator()(Sender&& s) const { // ---------------------------------------------------------------------------- +template +struct demo::when_any_t::state_env_base { + virtual auto get_receiver_env() const noexcept -> Env = 0; + ::demo::ex::inplace_stop_source source{}; +}; + +// ---------------------------------------------------------------------------- + template -struct demo::when_any_t::state_base { - ::std::size_t total{}; - Receiver receiver{}; +struct demo::when_any_t::state_base + : demo::when_any_t::state_env_base> +{ + ::std::size_t total; + Receiver receiver; ::std::atomic<::std::size_t> done_count{}; ::std::atomic<::std::size_t> ready_count{}; - ::demo::ex::inplace_stop_source source{}; + auto get_receiver_env() const noexcept -> ex::env_of_t override { + return ex::get_env(this->receiver); + } template - state_base(std::size_t tot, R&& rcvr) : total(tot), receiver(::std::forward(rcvr)) {} + state_base(::std::size_t tot, R&& r) + : total(tot) + , receiver(std::forward(r)) + { + } virtual ~state_base() = default; auto complete() -> bool { if (0u == this->done_count++) { @@ -292,13 +310,20 @@ struct demo::when_any_t::state_value : demo::when_any_t::state_base { // ---------------------------------------------------------------------------- -template +template struct demo::when_any_t::env { - demo::when_any_t::state_base* state; + demo::when_any_t::state_env_base* state; + auto query(const ex::get_stop_token_t&) const noexcept -> ex::inplace_stop_token { return this->state->source.get_token(); } - //-dk:TODO when_any_t::env: set up query forwarding + template + requires requires(const Query& q, const Env& e) { + q(e); + } + auto query(const Query& q) const noexcept { + return q(this->state->get_receiver_env()); + } }; // ---------------------------------------------------------------------------- @@ -308,7 +333,9 @@ struct demo::when_any_t::receiver { using receiver_concept = ::demo::ex::receiver_t; demo::when_any_t::state_value* state; - auto get_env() const noexcept -> env { return {this->state}; } + auto get_env() const noexcept -> demo::when_any_t::env> { + return {this->state}; + } template auto set_error(E&& error) && noexcept -> void { if (this->state->complete()) { @@ -349,7 +376,8 @@ struct demo::when_any_t::state<::std::index_sequence, Receiver, Value, Err state(R&& rcvr, P&& s) : state_value(sizeof...(Sender), ::std::forward(rcvr)), states{demo::ex::connect(::beman::net::detail::ex::detail::forward_like

(s.template get()), - receiver_type{this})...} {} + receiver_type{this})...} { + } state(state&&) = delete; auto start() & noexcept -> void { (demo::ex::start(this->states.template get()), ...); } }; @@ -360,26 +388,27 @@ template struct demo::when_any_t::sender { ::beman::execution::detail::product_type<::std::remove_cvref_t...> sender; using sender_concept = ex::sender_t; - template + template static consteval auto get_completion_signatures() { return ::beman::execution::detail::meta::unique< - ::beman::execution::detail::meta::combine())...>>(); + ::beman::execution::detail::meta::combine>())...>>(); } template - auto connect(Receiver&& receiver) && -> state< + auto connect(Receiver&& receiver) && { + using completion_signatures = decltype(ex::get_completion_signatures, decltype(ex::get_env(receiver))>()); + return state< ::std::index_sequence_for, ::std::remove_cvref_t, demo::detail::variant_from_list_t())>>>>, + completion_signatures>>>>, demo::detail::variant_from_list_t())>>, - Sender...> { - return {::std::forward(receiver), ::std::move(this->sender)}; + completion_signatures>>, + Sender...> {::std::forward(receiver), ::std::move(this->sender)}; } }; diff --git a/examples/postgres-talk.cpp b/examples/postgres-talk.cpp index 19b40da..54f7e58 100644 --- a/examples/postgres-talk.cpp +++ b/examples/postgres-talk.cpp @@ -121,6 +121,7 @@ auto main() -> int { ex::spawn(ex::starts_on(io.get_scheduler(), std::move(s)), scope.get_token()); }}; +#if 0 spawn(demo::http_server(io, 12345, [&spawn](auto client) noexcept { std::cout << "got a client\n"; spawn([](auto client)->ex::task{ @@ -129,37 +130,47 @@ auto main() -> int { std::cout << "client done\n"; }(std::move(client))); })); +#endif struct io_env { using scheduler_type = decltype(io.get_scheduler()); using error_types = ex::completion_signatures<>; }; - spawn([]()->ex::task { + auto timer{[]()->ex::task { while (true) { std::cout << "time=" << std::chrono::system_clock::now() << "\n"; co_await net::resume_after(co_await ex::read_env(ex::get_scheduler), 1s); } - }()); - if (false) { - spawn([](auto& conn, auto& ) noexcept ->ex::task { - pg::result res{co_await pg::exec(conn, query.c_str())}; - print_result(res); - //scope.request_stop(); - }(conn, scope)); + }()}; + + auto request{ + pg::exec2(conn, query.c_str()) + | ex::then(print_result) + | ex::upon_error([](pg::error e) noexcept { + std::cout << "database error=" << e << "\n"; + }) + }; + + if constexpr (false) { + spawn(std::move(timer)); + spawn(std::move(request) | ex::then([&scope]noexcept{ scope.request_stop(); })); + ex::sync_wait(ex::when_all(io.async_run(), scope.join())); + } + else if constexpr (false) { + ex::inplace_stop_source source; + ex::sync_wait(ex::when_all( + io.async_run(), + ex::starts_on(io.get_scheduler(), + ex::write_env(std::move(timer), ex::env{ex::prop{ex::get_stop_token, source.get_token()}})), + std::move(request) | ex::then([&source]noexcept{ source.request_stop(); }) + )); } else { - spawn( - pg::exec2(conn, query.c_str()) - //ex::just(pg::result(nullptr)) - //| ex::then([](auto) noexcept {}) - | ex::then(print_result) - //| ex::upon_error([](auto) noexcept {}) - | ex::upon_error([](pg::error e) noexcept { - std::cout << "database error=" << e << "\n"; - }) - ); + ex::sync_wait(demo::when_any( + io.async_run(), + std::move(request), + ex::starts_on(io.get_scheduler(), std::move(timer)) + )); } - - ex::sync_wait(ex::when_all(io.async_run(), scope.join())); } diff --git a/include/beman/net/detail/execution.hpp b/include/beman/net/detail/execution.hpp index 5d9b447..613b8a7 100644 --- a/include/beman/net/detail/execution.hpp +++ b/include/beman/net/detail/execution.hpp @@ -74,6 +74,7 @@ using ::beman::execution::then; using ::beman::execution::upon_error; using ::beman::execution::upon_stopped; using ::beman::execution::write_env; +using ::beman::execution::inline_scheduler; } // namespace beman::net::detail::ex // ---------------------------------------------------------------------------- diff --git a/include/beman/net/detail/io_context.hpp b/include/beman/net/detail/io_context.hpp index 422b0ac..cec3a6b 100644 --- a/include/beman/net/detail/io_context.hpp +++ b/include/beman/net/detail/io_context.hpp @@ -132,7 +132,9 @@ class beman::net::io_context { auto async_run_one() { return run_one_sender{this}; } auto async_run() { - return beman::execution::read_env(beman::execution::get_scheduler) | + return + beman::execution::write_env( + beman::execution::read_env(beman::execution::get_scheduler) | beman::execution::let_value([this, last_count = std::size_t(1)](auto sched) mutable noexcept { (void)last_count; //-dk:TODO remove this once no compiler complains about last_count being unused return beman::net::repeat_effect_until( @@ -143,7 +145,7 @@ class beman::net::io_context { last_count = count; })), [&last_count]() noexcept { return last_count == 0; }); - }) | + }), beman::execution::env{beman::execution::prop{beman::execution::get_stop_token, beman::execution::never_stop_token{}}}) | beman::execution::upon_error([](auto&&) noexcept {}); ; } diff --git a/include/beman/net/detail/repeat_effect_until.hpp b/include/beman/net/detail/repeat_effect_until.hpp index e87f5bc..926ca05 100644 --- a/include/beman/net/detail/repeat_effect_until.hpp +++ b/include/beman/net/detail/repeat_effect_until.hpp @@ -100,7 +100,7 @@ struct repeat_effect_until_t using sender_concept = beman::execution::sender_t; using completion_signatures = beman::execution::completion_signatures; - template + template static consteval auto get_completion_signatures() { return net::detail::merge_completion_signatures< completion_signatures, From 593329c0e5c197fdbb64b87470ad254fc56a5855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Mon, 4 May 2026 12:14:46 -0600 Subject: [PATCH 23/25] clang format --- examples/demo_algorithm.hpp | 53 ++-- examples/demo_http.hpp | 113 +++---- examples/demo_stream.hpp | 290 +++++++++--------- examples/memory-stream.cpp | 9 +- examples/postgres-talk.cpp | 114 +++---- examples/postgres.cpp | 50 ++- include/beman/net/detail/execution.hpp | 4 +- include/beman/net/detail/io_context.hpp | 30 +- .../detail/merge_completion_signatures.hpp | 77 +++-- .../beman/net/detail/repeat_effect_until.hpp | 42 +-- 10 files changed, 359 insertions(+), 423 deletions(-) diff --git a/examples/demo_algorithm.hpp b/examples/demo_algorithm.hpp index de5c7d5..e2bf392 100644 --- a/examples/demo_algorithm.hpp +++ b/examples/demo_algorithm.hpp @@ -242,30 +242,22 @@ inline auto demo::into_expected_t::operator()(Sender&& s) const { template struct demo::when_any_t::state_env_base { - virtual auto get_receiver_env() const noexcept -> Env = 0; + virtual auto get_receiver_env() const noexcept -> Env = 0; ::demo::ex::inplace_stop_source source{}; }; // ---------------------------------------------------------------------------- template -struct demo::when_any_t::state_base - : demo::when_any_t::state_env_base> -{ +struct demo::when_any_t::state_base : demo::when_any_t::state_env_base> { ::std::size_t total; Receiver receiver; ::std::atomic<::std::size_t> done_count{}; ::std::atomic<::std::size_t> ready_count{}; - auto get_receiver_env() const noexcept -> ex::env_of_t override { - return ex::get_env(this->receiver); - } + auto get_receiver_env() const noexcept -> ex::env_of_t override { return ex::get_env(this->receiver); } template - state_base(::std::size_t tot, R&& r) - : total(tot) - , receiver(std::forward(r)) - { - } + state_base(::std::size_t tot, R&& r) : total(tot), receiver(std::forward(r)) {} virtual ~state_base() = default; auto complete() -> bool { if (0u == this->done_count++) { @@ -318,9 +310,7 @@ struct demo::when_any_t::env { return this->state->source.get_token(); } template - requires requires(const Query& q, const Env& e) { - q(e); - } + requires requires(const Query& q, const Env& e) { q(e); } auto query(const Query& q) const noexcept { return q(this->state->get_receiver_env()); } @@ -333,9 +323,7 @@ struct demo::when_any_t::receiver { using receiver_concept = ::demo::ex::receiver_t; demo::when_any_t::state_value* state; - auto get_env() const noexcept -> demo::when_any_t::env> { - return {this->state}; - } + auto get_env() const noexcept -> demo::when_any_t::env> { return {this->state}; } template auto set_error(E&& error) && noexcept -> void { if (this->state->complete()) { @@ -376,8 +364,7 @@ struct demo::when_any_t::state<::std::index_sequence, Receiver, Value, Err state(R&& rcvr, P&& s) : state_value(sizeof...(Sender), ::std::forward(rcvr)), states{demo::ex::connect(::beman::net::detail::ex::detail::forward_like

(s.template get()), - receiver_type{this})...} { - } + receiver_type{this})...} {} state(state&&) = delete; auto start() & noexcept -> void { (demo::ex::start(this->states.template get()), ...); } }; @@ -390,25 +377,23 @@ struct demo::when_any_t::sender { using sender_concept = ex::sender_t; template static consteval auto get_completion_signatures() { - return ::beman::execution::detail::meta::unique< - ::beman::execution::detail::meta::combine>())...>>(); + return ::beman::execution::detail::meta::unique<::beman::execution::detail::meta::combine< + decltype(ex::get_completion_signatures>())...>>(); } template auto connect(Receiver&& receiver) && { - using completion_signatures = decltype(ex::get_completion_signatures, decltype(ex::get_env(receiver))>()); + using completion_signatures = decltype(ex::get_completion_signatures, + decltype(ex::get_env(receiver))>()); return state< - ::std::index_sequence_for, - ::std::remove_cvref_t, - demo::detail::variant_from_list_t>>>, - demo::detail::variant_from_list_t>, - Sender...> {::std::forward(receiver), ::std::move(this->sender)}; + ::std::index_sequence_for, + ::std::remove_cvref_t, + demo::detail::variant_from_list_t< + ex::detail::transform>>>, + demo::detail::variant_from_list_t>, + Sender...>{::std::forward(receiver), ::std::move(this->sender)}; } }; diff --git a/examples/demo_http.hpp b/examples/demo_http.hpp index 28bbefd..1497f4e 100644 --- a/examples/demo_http.hpp +++ b/examples/demo_http.hpp @@ -17,70 +17,71 @@ // ---------------------------------------------------------------------------- namespace demo::http { - namespace ex = ::beman::execution; - namespace net = ::beman::net; - - struct request { - std::string method; - std::string url; - std::string version; - std::vector headers; - }; - - template - struct http_client { - Stream stream; - public: - http_client(Stream s): stream{std::move(s)} {} - auto request() ->ex::task<> { - std::vector method, url, version; - if (!(co_await stream.read(method, ' '))) { - co_return; - } - std::cout << "read method='" << std::string_view(method) << "'\n"; - - if (!(co_await stream.read(url, ' '))) { - co_return; - } - std::cout << "read url='" << std::string_view(url) << "'\n"; - - if (!(co_await stream.read(version, "\r\n"))) { - co_return; - } - std::cout << "read version='" << std::string_view(version) << "'\n"; - - std::vector header; - while (co_await stream.read(header, "\r\n") && !header.empty()) { - std::cout << "read header line='" << std::string_view(header) << "'\n"; - header.clear(); - } - std::cout << "read HTTP GET request\n"; +namespace ex = ::beman::execution; +namespace net = ::beman::net; +struct request { + std::string method; + std::string url; + std::string version; + std::vector headers; +}; + +template +struct http_client { + Stream stream; + + public: + http_client(Stream s) : stream{std::move(s)} {} + auto request() -> ex::task<> { + std::vector method, url, version; + if (!(co_await stream.read(method, ' '))) { + co_return; + } + std::cout << "read method='" << std::string_view(method) << "'\n"; + + if (!(co_await stream.read(url, ' '))) { + co_return; + } + std::cout << "read url='" << std::string_view(url) << "'\n"; + + if (!(co_await stream.read(version, "\r\n"))) { co_return; } - }; - - struct no_error_env { - using error_types = ::beman::execution::completion_signatures<>; - }; - - template - ::beman::execution::task - http_server(::beman::net::io_context& io, unsigned short port, SenderFactory fun) { - ::beman::net::ip::tcp::endpoint ep(::beman::net::ip::address_v4::any(), port); - ::beman::net::ip::tcp::acceptor server(io, ep); - while (true) { - auto[client, addr] = co_await ::beman::net::async_accept(server); - std::cout << "connection from " << addr << "\n"; - fun(demo::http::http_client(demo::tcp_stream(std::move(client)))); + std::cout << "read version='" << std::string_view(version) << "'\n"; + + std::vector header; + while (co_await stream.read(header, "\r\n") && !header.empty()) { + std::cout << "read header line='" << std::string_view(header) << "'\n"; + header.clear(); } + std::cout << "read HTTP GET request\n"; + + co_return; + } +}; + +struct no_error_env { + using error_types = ::beman::execution::completion_signatures<>; +}; + +template +::beman::execution::task +http_server(::beman::net::io_context& io, unsigned short port, SenderFactory fun) { + ::beman::net::ip::tcp::endpoint ep(::beman::net::ip::address_v4::any(), port); + ::beman::net::ip::tcp::acceptor server(io, ep); + while (true) { + auto [client, addr] = co_await ::beman::net::async_accept(server); + std::cout << "connection from " << addr << "\n"; + fun(demo::http::http_client(demo::tcp_stream(std::move(client)))); } } +} // namespace demo::http namespace demo { - using http::http_client; - using http::http_server; -} +using http::http_client; +using http::http_server; +} // namespace demo // ---------------------------------------------------------------------------- diff --git a/examples/demo_stream.hpp b/examples/demo_stream.hpp index 4497394..a65c423 100644 --- a/examples/demo_stream.hpp +++ b/examples/demo_stream.hpp @@ -19,170 +19,160 @@ // ---------------------------------------------------------------------------- namespace demo::stream { - namespace ex = ::beman::execution; - namespace net = ::beman::net; - - struct tcp { - ::beman::net::ip::tcp::socket socket; - template - auto receive(Buffer buffer) { - return net::async_receive(this->socket, buffer); +namespace ex = ::beman::execution; +namespace net = ::beman::net; + +struct tcp { + ::beman::net::ip::tcp::socket socket; + template + auto receive(Buffer buffer) { + return net::async_receive(this->socket, buffer); + } +}; + +struct memory_base { + virtual ~memory_base() = default; + virtual auto add_receive_data(std::string_view data) -> std::size_t = 0; +}; +struct context { + using update_fun = std::function; + std::unordered_map streams; + std::vector> updates; + std::size_t next_index{}; + void next() { + auto& update = updates[next_index]; + memory_base* stream = streams[update.first]; + if (stream == nullptr || update.second(*stream)) { + this->updates.erase(this->updates.begin() + next_index); + } else if (this->updates.size() == ++this->next_index) { + this->next_index = 0u; } - }; - - struct memory_base { - virtual ~memory_base() = default; - virtual auto add_receive_data(std::string_view data) -> std::size_t = 0; - }; - struct context { - using update_fun = std::function; - std::unordered_map streams; - std::vector> updates; - std::size_t next_index{}; - void next() { - auto& update = updates[next_index]; - memory_base* stream = streams[update.first]; - if (stream == nullptr || update.second(*stream)) { - this->updates.erase(this->updates.begin() + next_index); - } - else if (this->updates.size() == ++this->next_index) { - this->next_index = 0u; - } + } + void add(std::string name, update_fun fun) { this->updates.emplace_back(std::move(name), std::move(fun)); } + auto async_run_one() { + return ex::just() | ex::then([this] { this->next(); }); + } + auto async_run() { + return ex::just() | + net::repeat_effect_until(ex::read_env(ex::get_scheduler) | ex::let_value([this](auto sched) { + return ex::schedule(sched) | + ex::let_value([this] { return this->async_run_one(); }); + }), + [this] { return this->updates.empty(); }); + } +}; +struct memory { + template + struct state : memory_base { + using operation_state_concept = ex::operation_state_t; + Receiver receiver; + Buffer buffer; + memory* self; + + template + state(R&& r, B&& b, memory* s) : receiver(std::forward(r)), buffer(std::forward(b)), self(s) {} + auto start() & noexcept { + this->self->receive_state = this; + this->self->ctxt->streams[this->self->name] = this; } - void add(std::string name, update_fun fun) { - this->updates.emplace_back(std::move(name), std::move(fun)); - } - auto async_run_one() { - return ex::just() | ex::then([this]{ this->next(); }); - } - auto async_run() { - return ex::just() | net::repeat_effect_until( - ex::read_env(ex::get_scheduler) - | ex::let_value([this](auto sched){ - return ex::schedule(sched) - | ex::let_value([this]{ return this->async_run_one(); }); - }), - [this]{ return this->updates.empty(); } - ); + auto add_receive_data(std::string_view data) -> std::size_t override { + const auto& vec{this->buffer.data()}; + std::size_t n{std::min(data.size(), vec[0].iov_len)}; + std::copy(data.begin(), data.begin() + n, static_cast(vec[0].iov_base)); + this->self->ctxt->streams[this->self->name] = nullptr; + ex::set_value(std::move(this->receiver), n); + return n; } }; - struct memory { - template - struct state - : memory_base - { - using operation_state_concept = ex::operation_state_t; - Receiver receiver; - Buffer buffer; - memory* self; - - template - state(R&& r, B&& b, memory* s) - : receiver(std::forward(r)) - , buffer(std::forward(b)) - , self(s) { - } - auto start() & noexcept { - this->self->receive_state = this; - this->self->ctxt->streams[this->self->name] = this; - } - auto add_receive_data(std::string_view data) -> std::size_t override { - const auto& vec{this->buffer.data()}; - std::size_t n{std::min(data.size(), vec[0].iov_len)}; - std::copy(data.begin(), data.begin() + n, static_cast(vec[0].iov_base)); - this->self->ctxt->streams[this->self->name] = nullptr; - ex::set_value(std::move(this->receiver), n); - return n; - } - }; - template - struct receive_sender { - using sender_concept = ex::sender_t; - template - static consteval auto get_completion_signatures() { - return ex::completion_signatures{}; - } - memory* self; - Buffer buffer; - - template - state, Buffer> connect(Receiver&& receiver) && { - static_assert(ex::operation_state, Buffer>>); - return {std::forward(receiver), std::move(this->buffer), this->self }; - } - }; - template - auto receive(Buffer buffer) { - static_assert(ex::sender>); - return receive_sender{this, std::move(buffer)}; + template + struct receive_sender { + using sender_concept = ex::sender_t; + template + static consteval auto get_completion_signatures() { + return ex::completion_signatures{}; } + memory* self; + Buffer buffer; - context* ctxt; - std::string name; - memory_base* receive_state{}; - memory(context& c, std::string n): ctxt(&c), name(std::move(n)) { + template + state, Buffer> connect(Receiver&& receiver) && { + static_assert(ex::operation_state, Buffer>>); + return {std::forward(receiver), std::move(this->buffer), this->self}; } }; - - template - struct basic_buffered { - using buffer_t = ::std::array; - using buffer_iterator = typename buffer_t::iterator; - - Stream stream; - buffer_t buffer{}; - buffer_iterator it{buffer.begin()}; - buffer_iterator end{buffer.begin()}; - - template - basic_buffered(Args&&... args): stream(std::forward(args)...) {} - static constexpr std::size_t length(char) { return 1u; } - bool consume(auto& to, char sentinel) { - auto end{::std::find(this->it, this->end, sentinel)}; - to.insert(to.end(), this->it, end); - this->it = end; - return end == this->end; + template + auto receive(Buffer buffer) { + static_assert(ex::sender>); + return receive_sender{this, std::move(buffer)}; + } + + context* ctxt; + std::string name; + memory_base* receive_state{}; + memory(context& c, std::string n) : ctxt(&c), name(std::move(n)) {} +}; + +template +struct basic_buffered { + using buffer_t = ::std::array; + using buffer_iterator = typename buffer_t::iterator; + + Stream stream; + buffer_t buffer{}; + buffer_iterator it{buffer.begin()}; + buffer_iterator end{buffer.begin()}; + + template + basic_buffered(Args&&... args) : stream(std::forward(args)...) {} + static constexpr std::size_t length(char) { return 1u; } + bool consume(auto& to, char sentinel) { + auto end{::std::find(this->it, this->end, sentinel)}; + to.insert(to.end(), this->it, end); + this->it = end; + return end == this->end; + } + template <::std::size_t Size> + static constexpr std::size_t length(const char (&)[Size]) { + return Size - 1u; + } + template <::std::size_t Size> + bool consume(auto& to, const char (&sentinel)[Size]) { + auto end{::std::search(this->it, this->end, sentinel, sentinel + Size - 1)}; + bool rc(end == this->end); + if (rc) { + end -= std::min(std::size_t(std::distance(this->it, end)), Size); } - template <::std::size_t Size> - static constexpr std::size_t length(const char (&)[Size]) { return Size - 1u; } - template <::std::size_t Size> - bool consume(auto& to, const char (&sentinel)[Size]) { - auto end{::std::search(this->it, this->end, sentinel, sentinel + Size - 1)}; - bool rc(end == this->end); - if (rc) { - end -= std::min(std::size_t(std::distance(this->it, end)), Size); + to.insert(to.end(), this->it, end); + this->it = end; + return rc; + } + auto read(auto& to, const auto& sentinel) -> ex::task { + while (this->consume(to, sentinel)) { + if (std::size_t(std::distance(this->end, this->buffer.end())) < this->buffer.size() / 2) { + this->end = std::move(this->it, this->end, this->buffer.begin()); + this->it = this->buffer.begin(); } - to.insert(to.end(), this->it, end); - this->it = end; - return rc; - } - auto read(auto& to, const auto& sentinel)->ex::task { - while (this->consume(to, sentinel)) { - if (std::size_t(std::distance(this->end, this->buffer.end())) < this->buffer.size() / 2) { - this->end = std::move(this->it, this->end, this->buffer.begin()); - this->it = this->buffer.begin(); - } - std::size_t n{co_await this->stream.receive(net::buffer(std::string_view(this->end, this->buffer.end())))}; - if (n == 0) { - co_return false; - } - this->end += n; + std::size_t n{co_await this->stream.receive(net::buffer(std::string_view(this->end, this->buffer.end())))}; + if (n == 0) { + co_return false; } - this->it += length(sentinel); - - co_return true; + this->end += n; } - }; -} + this->it += length(sentinel); + + co_return true; + } +}; +} // namespace demo::stream namespace demo { - using demo::stream::basic_buffered; - using tcp_stream = demo::stream::basic_buffered; - using mem_stream = demo::stream::basic_buffered; - using demo::stream::context; - using demo::stream::memory; - using demo::stream::memory_base; -} +using demo::stream::basic_buffered; +using tcp_stream = demo::stream::basic_buffered; +using mem_stream = demo::stream::basic_buffered; +using demo::stream::context; +using demo::stream::memory; +using demo::stream::memory_base; +} // namespace demo // ---------------------------------------------------------------------------- diff --git a/examples/memory-stream.cpp b/examples/memory-stream.cpp index 1eef4de..146ecae 100644 --- a/examples/memory-stream.cpp +++ b/examples/memory-stream.cpp @@ -21,8 +21,9 @@ namespace net = ::beman::net; int main() { std::cout << std::unitbuf; demo::context context; - std::string request("GET /some/url HTTP/1.1\r\nHost: localhost:12345\r\nUser-Agent: memory/0.0\r\nAccept: */*\r\n\r\n"); - context.add("foo", [data=std::string_view(request)](demo::memory_base& s) mutable { + std::string request( + "GET /some/url HTTP/1.1\r\nHost: localhost:12345\r\nUser-Agent: memory/0.0\r\nAccept: */*\r\n\r\n"); + context.add("foo", [data = std::string_view(request)](demo::memory_base& s) mutable { if (std::size_t n{s.add_receive_data(data.substr(0, std::min(std::size_t(2), data.size())))}) { data = data.substr(n); return false; @@ -30,9 +31,9 @@ int main() { return true; }); - auto reader{[](auto client)->ex::task<> { + auto reader{[](auto client) -> ex::task<> { co_await client.request(); }(demo::http_client(demo::mem_stream(context, "foo")))}; - ex::sync_wait(ex::when_all(context.async_run(), std::move(reader))); + ex::sync_wait(ex::when_all(context.async_run(), std::move(reader))); } diff --git a/examples/postgres-talk.cpp b/examples/postgres-talk.cpp index 54f7e58..df283c2 100644 --- a/examples/postgres-talk.cpp +++ b/examples/postgres-talk.cpp @@ -31,35 +31,33 @@ using namespace std::chrono_literals; // PQsetChunkedMode(PGconn *conn, int arg) - set chunked mode, return 0 on failure namespace { - const std::string connection_string("user=sruser dbname=sruser"); - const std::string query("select *, pg_sleep(0.5) from messages where 0 < key and key < 5;"); - - inline constexpr auto print_result{[](const PGresult* result) noexcept { - std::cout << "n=" << PQntuples(result) << ", m=" << PQnfields(result) << "\n"; - for (int i=0, n=PQntuples(result); i < n; ++i) { - for (int j=0, m=PQnfields(result); j < m; ++j == m || std::cout << ", ") { - std::cout << PQgetvalue(result, i, j); - } - std::cout << "\n"; +const std::string connection_string("user=sruser dbname=sruser"); +const std::string query("select *, pg_sleep(0.5) from messages where 0 < key and key < 5;"); + +inline constexpr auto print_result{[](const PGresult* result) noexcept { + std::cout << "n=" << PQntuples(result) << ", m=" << PQnfields(result) << "\n"; + for (int i = 0, n = PQntuples(result); i < n; ++i) { + for (int j = 0, m = PQnfields(result); j < m; ++j == m || std::cout << ", ") { + std::cout << PQgetvalue(result, i, j); } - }}; -} + std::cout << "\n"; + } +}}; +} // namespace namespace pg { struct connection { - std::unique_ptr conn; - net::ip::tcp::socket socket; - connection(net::io_context& io, PGconn* c): - conn(c? c: throw std::runtime_error("connection failed")), - socket(io, io.make_socket(PQsocket(conn.get()))) {} + std::unique_ptr conn; + net::ip::tcp::socket socket; + connection(net::io_context& io, PGconn* c) + : conn(c ? c : throw std::runtime_error("connection failed")), + socket(io, io.make_socket(PQsocket(conn.get()))) {} operator PGconn*() const { return conn.get(); } }; struct error { - std::string msg; - friend std::ostream& operator<< (std::ostream& out, const error& e) { - return out << e.msg; - } + std::string msg; + friend std::ostream& operator<<(std::ostream& out, const error& e) { return out << e.msg; } }; struct env { @@ -67,29 +65,21 @@ struct env { }; struct result { - std::unique_ptr res; - result(PGresult* r): res(r) {} + std::unique_ptr res; + result(PGresult* r) : res(r) {} operator PGresult*() const noexcept { return res.get(); } }; auto exec2(pg::connection& conn, const char* query) { - return - ex::just() - | ex::then([&conn, query] noexcept { PQsendQuery(conn, query); }) - | net::repeat_effect_until( - net::async_poll(conn.socket, net::event_type::out) - | ex::upon_error([](auto&&) noexcept {}) - | ex::then([](auto&&...) noexcept {}), - [&conn] noexcept { return not PQflush(conn); } - ) - | net::repeat_effect_until( - net::async_poll(conn.socket, net::event_type::in) - | ex::upon_error([](auto&&) noexcept {}) - | ex::then([&conn](auto&&...) noexcept { PQconsumeInput(conn); }), - [&conn] noexcept { return !PQisBusy(conn); } - ) - | ex::then([&conn] noexcept { return pg::result(PQgetResult(conn)); }) - ; + return ex::just() | ex::then([&conn, query] noexcept { PQsendQuery(conn, query); }) | + net::repeat_effect_until(net::async_poll(conn.socket, net::event_type::out) | + ex::upon_error([](auto&&) noexcept {}) | ex::then([](auto&&...) noexcept {}), + [&conn] noexcept { return not PQflush(conn); }) | + net::repeat_effect_until(net::async_poll(conn.socket, net::event_type::in) | + ex::upon_error([](auto&&) noexcept {}) | + ex::then([&conn](auto&&...) noexcept { PQconsumeInput(conn); }), + [&conn] noexcept { return !PQisBusy(conn); }) | + ex::then([&conn] noexcept { return pg::result(PQgetResult(conn)); }); } ex::task exec(pg::connection& conn, const char* query) { PQsendQuery(conn, query); @@ -108,18 +98,17 @@ ex::task exec(pg::connection& conn, const char* query) { } co_return std::move(res); } -} +} // namespace pg // ---------------------------------------------------------------------------- auto main() -> int { std::cout << std::unitbuf << "Postgres Example\n"; - net::io_context io; - pg::connection conn(io, PQconnectdb(connection_string.c_str())); + net::io_context io; + pg::connection conn(io, PQconnectdb(connection_string.c_str())); ex::counting_scope scope; - auto spawn{[&](ex::sender auto s){ - ex::spawn(ex::starts_on(io.get_scheduler(), std::move(s)), scope.get_token()); - }}; + auto spawn{ + [&](ex::sender auto s) { ex::spawn(ex::starts_on(io.get_scheduler(), std::move(s)), scope.get_token()); }}; #if 0 spawn(demo::http_server(io, 12345, [&spawn](auto client) noexcept { @@ -134,43 +123,32 @@ auto main() -> int { struct io_env { using scheduler_type = decltype(io.get_scheduler()); - using error_types = ex::completion_signatures<>; + using error_types = ex::completion_signatures<>; }; - auto timer{[]()->ex::task { + auto timer{[]() -> ex::task { while (true) { std::cout << "time=" << std::chrono::system_clock::now() << "\n"; co_await net::resume_after(co_await ex::read_env(ex::get_scheduler), 1s); } }()}; - auto request{ - pg::exec2(conn, query.c_str()) - | ex::then(print_result) - | ex::upon_error([](pg::error e) noexcept { - std::cout << "database error=" << e << "\n"; - }) - }; + auto request{pg::exec2(conn, query.c_str()) | ex::then(print_result) | + ex::upon_error([](pg::error e) noexcept { std::cout << "database error=" << e << "\n"; })}; if constexpr (false) { spawn(std::move(timer)); - spawn(std::move(request) | ex::then([&scope]noexcept{ scope.request_stop(); })); + spawn(std::move(request) | ex::then([&scope] noexcept { scope.request_stop(); })); ex::sync_wait(ex::when_all(io.async_run(), scope.join())); - } - else if constexpr (false) { + } else if constexpr (false) { ex::inplace_stop_source source; ex::sync_wait(ex::when_all( io.async_run(), ex::starts_on(io.get_scheduler(), - ex::write_env(std::move(timer), ex::env{ex::prop{ex::get_stop_token, source.get_token()}})), - std::move(request) | ex::then([&source]noexcept{ source.request_stop(); }) - )); - } - else { - ex::sync_wait(demo::when_any( - io.async_run(), - std::move(request), - ex::starts_on(io.get_scheduler(), std::move(timer)) - )); + ex::write_env(std::move(timer), ex::env{ex::prop{ex::get_stop_token, source.get_token()}})), + std::move(request) | ex::then([&source] noexcept { source.request_stop(); }))); + } else { + ex::sync_wait( + demo::when_any(io.async_run(), std::move(request), ex::starts_on(io.get_scheduler(), std::move(timer)))); } } diff --git a/examples/postgres.cpp b/examples/postgres.cpp index c52e277..95e6170 100644 --- a/examples/postgres.cpp +++ b/examples/postgres.cpp @@ -22,8 +22,7 @@ struct connection { handle_t handle; net::ip::tcp::socket socket; - connection(net::io_context& io, PGconn* conn) - : handle(conn), socket(io, io.make_socket(PQsocket(handle.get()))) { + connection(net::io_context& io, PGconn* conn) : handle(conn), socket(io, io.make_socket(PQsocket(handle.get()))) { if (PQstatus(conn) != CONNECTION_OK) { throw std::runtime_error(std::string("Connection to database failed: ") + PQerrorMessage(conn)); } @@ -44,9 +43,9 @@ struct error { }; struct result { - std::unique_ptr res; - explicit result(PGresult* r): res(r) {} - operator PGresult*() const { return this->res.get(); } + std::unique_ptr res; + explicit result(PGresult* r) : res(r) {} + operator PGresult*() const { return this->res.get(); } }; // PQsetnonblocking(const PGconn *conn, int arg) - set non-blocking mode to avoid write blocks @@ -127,37 +126,34 @@ int main() { pg::connection conn(io, PQconnectdb("user=sruser dbname=sruser")); ex::counting_scope scope; - auto spawn{[&](ex::sender auto s) { - ex::spawn(ex::starts_on(io.get_scheduler(), std::move(s)), scope.get_token()); - }}; + auto spawn{ + [&](ex::sender auto s) { ex::spawn(ex::starts_on(io.get_scheduler(), std::move(s)), scope.get_token()); }}; struct noexcept_env { - using error_types = ex::completion_signatures<>; + using error_types = ex::completion_signatures<>; using scheduler_type = decltype(io.get_scheduler()); }; - spawn(std::invoke( - []() noexcept -> ex::task { - while (true) { - std::cout << "time=" << std::chrono::system_clock::now() << "\n" << std::flush; - co_await net::resume_after(co_await ex::read_env(ex::get_scheduler), 1s); - } - })); + spawn(std::invoke([]() noexcept -> ex::task { + while (true) { + std::cout << "time=" << std::chrono::system_clock::now() << "\n" << std::flush; + co_await net::resume_after(co_await ex::read_env(ex::get_scheduler), 1s); + } + })); std::string query("select *, pg_sleep(1.1) from messages where 0 <= key and key < 3;"); spawn(pg::exec(conn, query) | ex::then([](pg::result res) noexcept { - for (int i{}, n{PQntuples(res)}; i < n; ++i) { - for (int j{}, m{PQnfields(res)}; j < m; ++j) { - std::cout << PQgetvalue(res, i, j) << ','; - } - std::cout << '\n'; - } - std::cout << "query done\n" << std::flush; - }) - | ex::upon_error([](pg::error error) noexcept { std::cout << "query error: " << error << "\n"; }) - | ex::then([&scope]() noexcept { scope.request_stop(); }) - ); + for (int i{}, n{PQntuples(res)}; i < n; ++i) { + for (int j{}, m{PQnfields(res)}; j < m; ++j) { + std::cout << PQgetvalue(res, i, j) << ','; + } + std::cout << '\n'; + } + std::cout << "query done\n" << std::flush; + }) | + ex::upon_error([](pg::error error) noexcept { std::cout << "query error: " << error << "\n"; }) | + ex::then([&scope]() noexcept { scope.request_stop(); })); ex::sync_wait(ex::when_all(scope.join(), io.async_run()) | ex::upon_stopped([]() noexcept {})); } catch (const pg::error& e) { diff --git a/include/beman/net/detail/execution.hpp b/include/beman/net/detail/execution.hpp index 613b8a7..ba43a59 100644 --- a/include/beman/net/detail/execution.hpp +++ b/include/beman/net/detail/execution.hpp @@ -27,8 +27,8 @@ using ::beman::execution::detail::decayed_tuple; using ::beman::execution::env; using ::beman::execution::env_of_t; using ::beman::execution::error_types_of_t; -using ::beman::execution::sends_stopped; using ::beman::execution::get_env; +using ::beman::execution::sends_stopped; using ::beman::execution::value_types_of_t; using ::beman::execution::get_completion_scheduler; @@ -65,6 +65,7 @@ using ::beman::execution::connect_t; using ::beman::execution::start; using ::beman::execution::start_t; +using ::beman::execution::inline_scheduler; using ::beman::execution::just; using ::beman::execution::just_error; using ::beman::execution::just_stopped; @@ -74,7 +75,6 @@ using ::beman::execution::then; using ::beman::execution::upon_error; using ::beman::execution::upon_stopped; using ::beman::execution::write_env; -using ::beman::execution::inline_scheduler; } // namespace beman::net::detail::ex // ---------------------------------------------------------------------------- diff --git a/include/beman/net/detail/io_context.hpp b/include/beman/net/detail/io_context.hpp index cec3a6b..d1f818d 100644 --- a/include/beman/net/detail/io_context.hpp +++ b/include/beman/net/detail/io_context.hpp @@ -132,20 +132,22 @@ class beman::net::io_context { auto async_run_one() { return run_one_sender{this}; } auto async_run() { - return - beman::execution::write_env( - beman::execution::read_env(beman::execution::get_scheduler) | - beman::execution::let_value([this, last_count = std::size_t(1)](auto sched) mutable noexcept { - (void)last_count; //-dk:TODO remove this once no compiler complains about last_count being unused - return beman::net::repeat_effect_until( - beman::execution::just(), - beman::execution::starts_on( - sched, - this->async_run_one() | beman::execution::then([&last_count](std::size_t count) noexcept { - last_count = count; - })), - [&last_count]() noexcept { return last_count == 0; }); - }), beman::execution::env{beman::execution::prop{beman::execution::get_stop_token, beman::execution::never_stop_token{}}}) | + return beman::execution::write_env( + beman::execution::read_env(beman::execution::get_scheduler) | + beman::execution::let_value([this, last_count = std::size_t(1)](auto sched) mutable noexcept { + (void)last_count; //-dk:TODO remove this once no compiler complains about last_count being + //unused + return beman::net::repeat_effect_until( + beman::execution::just(), + beman::execution::starts_on( + sched, + this->async_run_one() | + beman::execution::then( + [&last_count](std::size_t count) noexcept { last_count = count; })), + [&last_count]() noexcept { return last_count == 0; }); + }), + beman::execution::env{beman::execution::prop{beman::execution::get_stop_token, + beman::execution::never_stop_token{}}}) | beman::execution::upon_error([](auto&&) noexcept {}); ; } diff --git a/include/beman/net/detail/merge_completion_signatures.hpp b/include/beman/net/detail/merge_completion_signatures.hpp index 7a9b73d..499c028 100644 --- a/include/beman/net/detail/merge_completion_signatures.hpp +++ b/include/beman/net/detail/merge_completion_signatures.hpp @@ -7,51 +7,44 @@ // ---------------------------------------------------------------------------- namespace beman::net::detail { - template struct merge_completion_signatures_unique; - template - struct merge_completion_signatures_unique> { - using type = S0; - }; - template - struct merge_completion_signatures_unique< - ::beman::execution::completion_signatures, - ::beman::execution::completion_signatures - > { - using type = std::conditional_t, - ::beman::execution::completion_signatures - >; - }; - template - struct merge_completion_signatures_unique< - S0, - ::beman::execution::completion_signatures - > { - using type = typename merge_completion_signatures_unique< - typename merge_completion_signatures_unique>::type, - ::beman::execution::completion_signatures - >::type; - }; +template +struct merge_completion_signatures_unique; +template +struct merge_completion_signatures_unique> { + using type = S0; +}; +template +struct merge_completion_signatures_unique<::beman::execution::completion_signatures, + ::beman::execution::completion_signatures> { + using type = std::conditional_t, + ::beman::execution::completion_signatures>; +}; +template +struct merge_completion_signatures_unique> { + using type = typename merge_completion_signatures_unique< + typename merge_completion_signatures_unique>::type, + ::beman::execution::completion_signatures>::type; +}; - template struct merge_completion_signatures_helper; - template - struct merge_completion_signatures_helper - { - using type = typename merge_completion_signatures_unique::type; - }; - template - struct merge_completion_signatures_helper { - using type = typename merge_completion_signatures_helper< - typename merge_completion_signatures_helper::type, - S2, - Sigs...>::type; - }; +template +struct merge_completion_signatures_helper; +template +struct merge_completion_signatures_helper { + using type = typename merge_completion_signatures_unique::type; +}; +template +struct merge_completion_signatures_helper { + using type = typename merge_completion_signatures_helper::type, + S2, + Sigs...>::type; +}; - template - inline consteval auto merge_completion_signatures() { - return typename merge_completion_signatures_helper::type(); - } +template +inline consteval auto merge_completion_signatures() { + return typename merge_completion_signatures_helper::type(); } +} // namespace beman::net::detail // ---------------------------------------------------------------------------- diff --git a/include/beman/net/detail/repeat_effect_until.hpp b/include/beman/net/detail/repeat_effect_until.hpp index 926ca05..4c07e16 100644 --- a/include/beman/net/detail/repeat_effect_until.hpp +++ b/include/beman/net/detail/repeat_effect_until.hpp @@ -13,9 +13,7 @@ // ---------------------------------------------------------------------------- namespace beman::net::detail { -struct repeat_effect_until_t - : beman::execution::sender_adaptor_closure -{ +struct repeat_effect_until_t : beman::execution::sender_adaptor_closure { template auto operator()(Upstream&& upstream, Body&& body, Predicate&& predicate) const { return sender, std::remove_cvref_t, std::remove_cvref_t>{ @@ -23,20 +21,15 @@ struct repeat_effect_until_t } template - struct adaptor - : beman::execution::sender_adaptor_closure> - { - std::remove_cvref_t body; + struct adaptor : beman::execution::sender_adaptor_closure> { + std::remove_cvref_t body; std::remove_cvref_t predicate; template - adaptor(B&& b, P&& p): body(std::forward(b)), predicate(std::forward

(p)) {} + adaptor(B&& b, P&& p) : body(std::forward(b)), predicate(std::forward

(p)) {} template auto operator()(Upstream&& upstream) && { return repeat_effect_until_t{}( - std::forward(upstream), - std::move(this->body), - std::move(this->predicate) - ); + std::forward(upstream), std::move(this->body), std::move(this->predicate)); } }; template @@ -98,23 +91,19 @@ struct repeat_effect_until_t template struct sender { using sender_concept = beman::execution::sender_t; - using completion_signatures = - beman::execution::completion_signatures; + using completion_signatures = beman::execution::completion_signatures; template static consteval auto get_completion_signatures() { return net::detail::merge_completion_signatures< - completion_signatures, - ex::error_types_of_t, ex::completion_signatures>, - ex::error_types_of_t, ex::completion_signatures>, - std::conditional_t || ex::sends_stopped, - ex::completion_signatures, - ex::completion_signatures<> - >, + completion_signatures, + ex::error_types_of_t, ex::completion_signatures>, + ex::error_types_of_t, ex::completion_signatures>, + std::conditional_t || ex::sends_stopped, + ex::completion_signatures, + ex::completion_signatures<>>, std::conditional_t()()), - ex::completion_signatures<>, - ex::completion_signatures - > - >(); + ex::completion_signatures<>, + ex::completion_signatures>>(); } Upstream upstream; @@ -122,7 +111,8 @@ struct repeat_effect_until_t Predicate predicate; template - auto connect(Receiver&& receiver) noexcept(std::is_nothrow_constructible_v, Receiver>) { + auto connect(Receiver&& receiver) noexcept( + std::is_nothrow_constructible_v, Receiver>) { return state>{std::move(this->upstream), std::move(this->body), std::move(this->predicate), From 361c87732b3efccfa2ce468e947963b2439de95c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Mon, 4 May 2026 14:41:10 -0600 Subject: [PATCH 24/25] restored building examples --- examples/CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 64fee27..1292580 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -37,12 +37,13 @@ set(EXAMPLES if(${HAS_POSTGRES}) list(APPEND EXAMPLES postgres + postgres-talk populate-postgres ) endif() set(xEXAMPLES taps) -set(EXAMPLES memory-stream) -set(EXAMPLES postgres-talk) +set(xEXAMPLES memory-stream) +set(xEXAMPLES postgres-talk) foreach(EXAMPLE ${EXAMPLES}) set(EXAMPLE_TARGET beman.net.examples.${EXAMPLE}) From 48857e6a1a5d0a3105957f5ea1910e7c942f3d21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Mon, 4 May 2026 15:06:09 -0600 Subject: [PATCH 25/25] fix/work around issues located by CI --- examples/CMakeLists.txt | 11 +++++------ examples/demo_algorithm.hpp | 8 ++++---- include/beman/net/detail/io_context.hpp | 2 +- include/beman/net/detail/iocp_context.hpp | 3 +++ include/beman/net/detail/repeat_effect_until.hpp | 2 +- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1292580..dd3ae61 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -35,11 +35,7 @@ set(EXAMPLES ) if(${HAS_POSTGRES}) -list(APPEND EXAMPLES - postgres - postgres-talk - populate-postgres -) + list(APPEND EXAMPLES postgres postgres-talk populate-postgres) endif() set(xEXAMPLES taps) set(xEXAMPLES memory-stream) @@ -53,7 +49,10 @@ foreach(EXAMPLE ${EXAMPLES}) endif() target_sources(${EXAMPLE_TARGET} PRIVATE ${EXAMPLE}.cpp) if(${HAS_POSTGRES}) - target_include_directories(${EXAMPLE_TARGET} PRIVATE ${POSTGRESROOT}/include) + target_include_directories( + ${EXAMPLE_TARGET} + PRIVATE ${POSTGRESROOT}/include + ) target_link_directories(${EXAMPLE_TARGET} PRIVATE ${POSTGRESROOT}/lib) target_link_libraries(${EXAMPLE_TARGET} PRIVATE pq) endif() diff --git a/examples/demo_algorithm.hpp b/examples/demo_algorithm.hpp index e2bf392..ecb681f 100644 --- a/examples/demo_algorithm.hpp +++ b/examples/demo_algorithm.hpp @@ -250,10 +250,10 @@ struct demo::when_any_t::state_env_base { template struct demo::when_any_t::state_base : demo::when_any_t::state_env_base> { - ::std::size_t total; - Receiver receiver; - ::std::atomic<::std::size_t> done_count{}; - ::std::atomic<::std::size_t> ready_count{}; + ::std::size_t total; + Receiver receiver; + ::std::atomic<::std::size_t> done_count{}; + ::std::atomic<::std::size_t> ready_count{}; auto get_receiver_env() const noexcept -> ex::env_of_t override { return ex::get_env(this->receiver); } template diff --git a/include/beman/net/detail/io_context.hpp b/include/beman/net/detail/io_context.hpp index d1f818d..7490db5 100644 --- a/include/beman/net/detail/io_context.hpp +++ b/include/beman/net/detail/io_context.hpp @@ -136,7 +136,7 @@ class beman::net::io_context { beman::execution::read_env(beman::execution::get_scheduler) | beman::execution::let_value([this, last_count = std::size_t(1)](auto sched) mutable noexcept { (void)last_count; //-dk:TODO remove this once no compiler complains about last_count being - //unused + // unused return beman::net::repeat_effect_until( beman::execution::just(), beman::execution::starts_on( diff --git a/include/beman/net/detail/iocp_context.hpp b/include/beman/net/detail/iocp_context.hpp index 33756de..33fa685 100644 --- a/include/beman/net/detail/iocp_context.hpp +++ b/include/beman/net/detail/iocp_context.hpp @@ -308,6 +308,9 @@ struct iocp_context final : context_base { ::PostQueuedCompletionStatus(iocp_handle, 0, 0, nullptr); } + auto poll(poll_operation* op) -> submit_result override { + return submit_result{}; //-dk:TODO + } auto accept(accept_operation* op) -> submit_result override { SOCKET listen_socket = static_cast(native_handle(op->id)); int family = sockets[op->id].address_family; diff --git a/include/beman/net/detail/repeat_effect_until.hpp b/include/beman/net/detail/repeat_effect_until.hpp index 4c07e16..22887e0 100644 --- a/include/beman/net/detail/repeat_effect_until.hpp +++ b/include/beman/net/detail/repeat_effect_until.hpp @@ -90,7 +90,7 @@ struct repeat_effect_until_t : beman::execution::sender_adaptor_closure struct sender { - using sender_concept = beman::execution::sender_t; + using sender_concept = beman::execution::sender_t; using completion_signatures = beman::execution::completion_signatures; template static consteval auto get_completion_signatures() {