[Capy review] On integrating nonblocking C libraries with corosio
Corosio's tcp_socket::wait docs state: "Wait for the socket to become ready in a given direction. [...] useful for integrating with C libraries that own the I/O on a nonblocking fd and only need readiness notification (e.g. libpq async, libssh)." I know that Klemens wrote a libssh wrapper [1] using this. Since the docs mention libpq, I thought I would give it a try. libssh allows setting a user-supplied socket using ssh_socket_set_fd [2]. This works great with tcp_socket::wait: you create your corosio socket and call ssh_socket_set_fd(sock.native_handle()). libpq gives you a read-only socket handle with PQsocket [3]. This means that the pattern above does not work here. With Asio, I can write this: asio::awaitable<void> co_main() { PGconn* conn = PQconnectStart( "host=127.0.0.1 dbname=postgres user=postgres password=secret sslmode=disable"); if (!conn) { std::cerr << "Failed PQconnectStart\n"; exit(1); } struct deleter { void operator()(asio::ip::tcp::socket* d) const { boost::system::error_code ec; d->release(ec); } }; asio::ip::tcp::socket descr{co_await asio::this_coro::executor}; bool finished = false; while (!finished) { switch (PQconnectPoll(conn)) { case PGRES_POLLING_READING: { std::cout << "wait_read\n"; descr.assign(asio::ip::tcp::v4(), PQsocket(conn)); std::unique_ptr<asio::ip::tcp::socket, deleter> guard{&descr}; co_await descr.async_wait(asio::ip::tcp::socket::wait_read); break; } case PGRES_POLLING_WRITING: { std::cout << "wait_write\n"; descr.assign(asio::ip::tcp::v4(), PQsocket(conn)); std::unique_ptr<asio::ip::tcp::socket, deleter> guard{&descr}; co_await descr.async_wait(asio::ip::tcp::socket::wait_write); break; } case PGRES_POLLING_FAILED: std::cerr << "Poll failed\n"; finished = true; break; case PGRES_POLLING_OK: default: finished = true; break; } } PQfinish(conn); co_return; } Which is a bit awful, but does work in Linux, and I think _should_ happen to work on Windows, too. Points to note here: * PQsocket returns a _non owning_ socket handle. Asio (and corosio) sockets are owning. We need to take extra steps to avoid the socket being closed. * PQsocket might return a different socket at each call. This happens when trying several servers until one works. * PQsocket returns an int even in Windows, where SOCKET is unsigned. If the returned socket is valid, the value on Windows is the actual SOCKET, cast to an int [4]. * asio::tcp::socket::assign takes a native_handle_type, which is an int on POSIX, and a class constructible from SOCKET on Windows [5]. The problem I've found with Corosio is that (by design) there is no tcp_socket::assign (because this takes a native type), so this pattern doesn't work. Considering that the docs mention libpq explicitly, what am I missing here? Note that libmariadb [6] and libmosquitto [7] follow libpq's pattern. Thanks, Ruben. [1] https://github.com/klemens-morgenstern/corosh [2] https://api.libssh.org/stable/group__libssh__socket.html [3] https://www.postgresql.org/docs/current/libpq-status.html [4] https://github.com/postgres/postgres/blob/56b2792cf84fc78f9109b0ae122174e703... [5] https://github.com/boostorg/asio/blob/cba4d9791d77264814eccb53324d5d37669467... [6] https://mariadb.com/docs/server/reference/product-development/mariadb-intern... [7] https://mosquitto.org/api2/files/mosquitto-h.html#mosquitto_socket
On Wed, Jun 24, 2026 at 8:19 AM Ruben Perez via Boost <boost@lists.boost.org> wrote:
Corosio's tcp_socket::wait docs state:
I like your enthusiasm and assumption that Corosio has feature-parity with Asio but lets remember, Rome was not built in a day. Expecting Corosio to have literally everything from Asio is a big ask. We only implemented the most common things, so that the library could mature and we can make better informed decisions on what to do with the rest, Thanks
participants (2)
-
Ruben Perez -
Vinnie Falco