From b4b8935363c7ff29f714b931cb9704dd424acf9b Mon Sep 17 00:00:00 2001 From: "Jeroen T. Vermeulen" Date: Thu, 30 Sep 2021 19:28:13 +0200 Subject: [PATCH] Support nonblocking connection. See #487. We used to have a `connection` class _hierarchy,_ and one of those did something similar. But this new class is much more limited in scope and keeps things simple. In the future hopefully this can form a basis for C++20 async support. But for now, any application with a wait loop based on `poll()` or `select()` should be able to use it. --- Makefile.in | 9 +- NEWS | 1 + aclocal.m4 | 13 ++- config-tests/concepts.cxx | 3 +- config/Makefile.in | 2 +- config/ltmain.sh | 4 +- config/m4/libtool.m4 | 4 +- configure | 11 ++- doc/Makefile.in | 2 +- include/Makefile.in | 2 +- include/pqxx/Makefile.in | 2 +- include/pqxx/connection.hxx | 101 ++++++++++++++++++--- include/pqxx/strconv.hxx | 4 +- include/pqxx/types.hxx | 7 +- include/pqxx/util.hxx | 3 +- include/pqxx/zview.hxx | 6 +- src/Makefile.in | 2 +- src/connection.cxx | 120 +++++++++++++------------ test/Makefile.am | 1 + test/Makefile.in | 10 ++- test/unit/test_connection.cxx | 4 +- test/unit/test_nonblocking_connect.cxx | 26 ++++++ tools/Makefile.in | 2 +- 23 files changed, 230 insertions(+), 109 deletions(-) mode change 100755 => 100644 config/ltmain.sh create mode 100644 test/unit/test_nonblocking_connect.cxx diff --git a/Makefile.in b/Makefile.in index d3eb30fc1..542c5ebbe 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.16.3 from Makefile.am. +# Makefile.in generated by automake 1.16.2 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. @@ -346,7 +346,6 @@ am__set_TESTS_bases = \ bases='$(TEST_LOGS)'; \ bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \ bases=`echo $$bases` -AM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING)' RECHECK_LOGS = $(TEST_LOGS) TEST_SUITE_LOG = test-suite.log TEST_EXTENSIONS = @EXEEXT@ .test @@ -416,8 +415,6 @@ am__relativize = \ DIST_ARCHIVES = $(distdir).tar.gz GZIP_ENV = --best DIST_TARGETS = dist-gzip -# Exists only to be overridden by the user if desired. -AM_DISTCHECK_DVI_TARGET = dvi distuninstallcheck_listfiles = find . -type f -print am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \ | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$' @@ -848,7 +845,7 @@ $(TEST_SUITE_LOG): $(TEST_LOGS) test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \ fi; \ echo "$${col}$$br$${std}"; \ - echo "$${col}Testsuite summary"$(AM_TESTSUITE_SUMMARY_HEADER)"$${std}"; \ + echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}"; \ echo "$${col}$$br$${std}"; \ create_testsuite_report --maybe-color; \ echo "$$col$$br$$std"; \ @@ -1053,7 +1050,7 @@ distcheck: dist $(DISTCHECK_CONFIGURE_FLAGS) \ --srcdir=../.. --prefix="$$dc_install_base" \ && $(MAKE) $(AM_MAKEFLAGS) \ - && $(MAKE) $(AM_MAKEFLAGS) $(AM_DISTCHECK_DVI_TARGET) \ + && $(MAKE) $(AM_MAKEFLAGS) dvi \ && $(MAKE) $(AM_MAKEFLAGS) check \ && $(MAKE) $(AM_MAKEFLAGS) install \ && $(MAKE) $(AM_MAKEFLAGS) installcheck \ diff --git a/NEWS b/NEWS index 5bbab9c49..b9c61fcb6 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ - Fix warnings about `[[likely]]` in `if constexpr`. (#475) - Clearer error for ambiguous string conversion of `char` type. (#481) - Pseudo-statement in `prepare()` error was for the wrong statement. (#488) + - New class, `connecting` for nonblocking connection to the database. (#487) 7.6.0 - Removed bad string conversion to `std::basic_string_view`. (#463) - Add C++20 concepts: `binary`, `char_string`, `char_strings`. diff --git a/aclocal.m4 b/aclocal.m4 index 632e40703..d4439da60 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -1,4 +1,4 @@ -# generated automatically by aclocal 1.16.3 -*- Autoconf -*- +# generated automatically by aclocal 1.16.2 -*- Autoconf -*- # Copyright (C) 1996-2020 Free Software Foundation, Inc. @@ -1020,7 +1020,7 @@ AC_DEFUN([AM_AUTOMAKE_VERSION], [am__api_version='1.16' dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to dnl require some minimum version. Point them to the right macro. -m4_if([$1], [1.16.3], [], +m4_if([$1], [1.16.2], [], [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl ]) @@ -1036,7 +1036,7 @@ m4_define([_AM_AUTOCONF_VERSION], []) # Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. # This function is AC_REQUIREd by AM_INIT_AUTOMAKE. AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], -[AM_AUTOMAKE_VERSION([1.16.3])dnl +[AM_AUTOMAKE_VERSION([1.16.2])dnl m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl _AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) @@ -1724,7 +1724,12 @@ AC_DEFUN([AM_MISSING_HAS_RUN], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl AC_REQUIRE_AUX_FILE([missing])dnl if test x"${MISSING+set}" != xset; then - MISSING="\${SHELL} '$am_aux_dir/missing'" + case $am_aux_dir in + *\ * | *\ *) + MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; + *) + MISSING="\${SHELL} $am_aux_dir/missing" ;; + esac fi # Use eval to expand $SHELL if eval "$MISSING --is-lightweight"; then diff --git a/config-tests/concepts.cxx b/config-tests/concepts.cxx index 5589b4e58..fb6630426 100644 --- a/config-tests/concepts.cxx +++ b/config-tests/concepts.cxx @@ -4,8 +4,7 @@ #include -template -concept Foo = std::ranges::input_range; +template concept Foo = std::ranges::input_range; template auto foo(F const &r) diff --git a/config/Makefile.in b/config/Makefile.in index b94cf57c4..6eac80239 100644 --- a/config/Makefile.in +++ b/config/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.16.3 from Makefile.am. +# Makefile.in generated by automake 1.16.2 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. diff --git a/config/ltmain.sh b/config/ltmain.sh old mode 100755 new mode 100644 index 21e5e0784..0cb7f90d3 --- a/config/ltmain.sh +++ b/config/ltmain.sh @@ -31,7 +31,7 @@ PROGRAM=libtool PACKAGE=libtool -VERSION="2.4.6 Debian-2.4.6-15" +VERSION="2.4.6 Debian-2.4.6-14" package_revision=2.4.6 @@ -2141,7 +2141,7 @@ include the following information: compiler: $LTCC compiler flags: $LTCFLAGS linker: $LD (gnu? $with_gnu_ld) - version: $progname $scriptversion Debian-2.4.6-15 + version: $progname $scriptversion Debian-2.4.6-14 automake: `($AUTOMAKE --version) 2>/dev/null |$SED 1q` autoconf: `($AUTOCONF --version) 2>/dev/null |$SED 1q` diff --git a/config/m4/libtool.m4 b/config/m4/libtool.m4 index c4c02946d..a6d21ae56 100644 --- a/config/m4/libtool.m4 +++ b/config/m4/libtool.m4 @@ -1071,11 +1071,11 @@ _LT_EOF # to the OS version, if on x86, and 10.4, the deployment # target defaults to 10.4. Don't you love it? case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in - 10.0,*86*-darwin8*|10.0,*-darwin[[912]]*) + 10.0,*86*-darwin8*|10.0,*-darwin[[91]]*) _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; 10.[[012]][[,.]]*) _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; - 10.*|11.*) + 10.*) _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; esac ;; diff --git a/configure b/configure index 94c3a11bb..7b4061ddd 100755 --- a/configure +++ b/configure @@ -3978,7 +3978,12 @@ program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"` am_aux_dir=`cd "$ac_aux_dir" && pwd` if test x"${MISSING+set}" != xset; then - MISSING="\${SHELL} '$am_aux_dir/missing'" + case $am_aux_dir in + *\ * | *\ *) + MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; + *) + MISSING="\${SHELL} $am_aux_dir/missing" ;; + esac fi # Use eval to expand $SHELL if eval "$MISSING --is-lightweight"; then @@ -9026,11 +9031,11 @@ $as_echo "$lt_cv_ld_force_load" >&6; } # to the OS version, if on x86, and 10.4, the deployment # target defaults to 10.4. Don't you love it? case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in - 10.0,*86*-darwin8*|10.0,*-darwin[912]*) + 10.0,*86*-darwin8*|10.0,*-darwin[91]*) _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; 10.[012][,.]*) _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; - 10.*|11.*) + 10.*) _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; esac ;; diff --git a/doc/Makefile.in b/doc/Makefile.in index cf4024849..17ea6b080 100644 --- a/doc/Makefile.in +++ b/doc/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.16.3 from Makefile.am. +# Makefile.in generated by automake 1.16.2 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. diff --git a/include/Makefile.in b/include/Makefile.in index d700e4d29..7e97b380a 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.16.3 from Makefile.am. +# Makefile.in generated by automake 1.16.2 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. diff --git a/include/pqxx/Makefile.in b/include/pqxx/Makefile.in index 988820bd8..f2a033837 100644 --- a/include/pqxx/Makefile.in +++ b/include/pqxx/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.16.3 from Makefile.am. +# Makefile.in generated by automake 1.16.2 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. diff --git a/include/pqxx/connection.hxx b/include/pqxx/connection.hxx index 735255998..581c140ec 100644 --- a/include/pqxx/connection.hxx +++ b/include/pqxx/connection.hxx @@ -81,12 +81,14 @@ concept ZKey_ZValues = std::ranges::input_range and requires(T t) {std::cbegin(t)}; { std::get<0>(*std::cbegin(t)) - } -> ZString; + } + ->ZString; { std::get<1>(*std::cbegin(t)) - } -> ZString; -} and std::tuple_size_v::value_type> -== 2; + } + ->ZString; +} +and std::tuple_size_v::value_type> == 2; #endif // PQXX_HAVE_CONCEPTS } // namespace pqxx::internal @@ -883,15 +885,27 @@ public: void close(); private: + friend class connecting; + enum connect_mode + { + connect_nonblocking + }; + connection(connect_mode, zview connection_string); + + /// Poll for ongoing connection, try to progress towards completion. + /** Returns a pair of "now please wait to read data from socket" and "now + * please wait to write data to socket." Both will be false when done. + * + * Throws an exception if polling indicates that the connection has failed. + */ + std::pair poll_connect(); + // Initialise based on connection string. void init(char const options[]); // Initialise based on parameter names and values. void init(char const *params[], char const *values[]); void complete_init(); - void wait_read() const; - void wait_read(std::time_t seconds, long microseconds) const; - result make_result( internal::pq::PGresult *pgr, std::shared_ptr const &query, std::string_view desc = ""sv); @@ -985,6 +999,71 @@ private: using connection_base = connection; +/// An ongoing, non-blocking stepping stone to a connection. +/** Use this when you want to create a connection to the database, but without + * blocking your whole thread. + * + * Connecting in this way is probably not "faster" (it's more complicated and + * has some extra overhead), but in some situations you can use it to make your + * application as a whole faster. It all depends on having other useful work + * to do in the same thread, and being able to wait on a socket. If you have + * other I/O going on at the same time, your event loop can wait for both the + * libpqxx socket and your own sockets, and wake up whenever any of them is + * ready to do work. + * + * Connecting in this way is not properly "asynchronous;" it's merely + * "nonblocking." This means it's not a super-high-performance mechanism like + * you might get with e.g. @c io_uring. In particular, if we need to look up + * the database hostname in DNS, that will happen synchronously. + * + * To use this, create the @c connecting object, passing a connection string. + * Then loop: If @c wait_to_read() returns true, wait for the socket to have + * incoming data on it. If @c wait_to_write() returns true, wait wait for the + * socket to be ready for writing. Repeat until @c done() returns true (or + * there is an exception). Finally, call @c produce() to get the completed + * connection. This will work only once on the @c connecting object. + */ +class PQXX_LIBEXPORT connecting +{ +public: + /// Start connecting. + connecting(zview connection_string) : + m_conn{connection::connect_nonblocking, connection_string} + {} + connecting() : m_conn{connection::connect_nonblocking, ""_zv} {} + + /// Get the socket. This may change during the process. + int sock() const noexcept { return m_conn.sock(); } + + /// Should we currently wait to @i read from the socket? + bool wait_to_read() const noexcept { return m_reading; } + + /// Should we currently wait to @i write to the socket? + bool wait_to_write() const noexcept { return m_writing; } + + /// Progress towards completion but don't block. + void process() + { + auto const [reading, writing]{m_conn.poll_connect()}; + m_reading = reading; + m_writing = writing; + } + + /// Is our connection finished? + bool done() const noexcept { return not m_reading and not m_writing; } + + /// Produce the completed connection object. + /** Use this only once, after @c done() returned @c true. + */ + connection produce(); + +private: + connection m_conn; + bool m_reading{false}; + bool m_writing{true}; +}; + + template inline std::string connection::quote(T const &t) const { if constexpr (nullness::always_null) @@ -1050,10 +1129,10 @@ inline connection::connection(MAPPING const ¶ms) namespace pqxx::internal { -PQXX_LIBEXPORT void wait_read(internal::pq::PGconn const *); -PQXX_LIBEXPORT void wait_read( - internal::pq::PGconn const *, std::time_t seconds, long microseconds); -PQXX_LIBEXPORT void wait_write(internal::pq::PGconn const *); +/// Wait for a socket to be ready for reading/writing, or timeout. +PQXX_LIBEXPORT void wait_fd( + int fd, bool for_read, bool for_write, unsigned seconds = 1, + unsigned microseconds = 0); } // namespace pqxx::internal #include "pqxx/internal/compiler-internal-post.hxx" diff --git a/include/pqxx/strconv.hxx b/include/pqxx/strconv.hxx index de95c58ea..b00c9ce48 100644 --- a/include/pqxx/strconv.hxx +++ b/include/pqxx/strconv.hxx @@ -424,8 +424,8 @@ template inline constexpr format param_format(TYPE const &) * we can reference them by a pointer. */ template -concept binary = std::ranges::contiguous_range and - std::is_same_v>, std::byte>; +concept binary = std::ranges::contiguous_range + and std::is_same_v>, std::byte>; #endif //@} } // namespace pqxx diff --git a/include/pqxx/types.hxx b/include/pqxx/types.hxx index 7047c483a..f1e88c50b 100644 --- a/include/pqxx/types.hxx +++ b/include/pqxx/types.hxx @@ -107,9 +107,8 @@ using value_type = decltype(*std::begin(std::declval())); #if defined(PQXX_HAVE_CONCEPTS) /// Concept: Any type that we can read as a string of @c char. template -concept char_string = std::ranges::contiguous_range and - std::same_as < strip_t>, -char > ; +concept char_string = std::ranges::contiguous_range + and std::same_as>, char>; /// Concept: Anything we can iterate to get things we can read as strings. template @@ -119,7 +118,7 @@ concept char_strings = /// Concept: Anything we might want to treat as binary data. template concept potential_binary = std::ranges::contiguous_range and - (sizeof(value_type) == 1); + (sizeof(value_type) == 1); #endif // PQXX_HAVE_CONCEPTS diff --git a/include/pqxx/util.hxx b/include/pqxx/util.hxx index 3248b409b..df050a6f1 100644 --- a/include/pqxx/util.hxx +++ b/include/pqxx/util.hxx @@ -239,8 +239,7 @@ std::basic_string_view binary_cast(TYPE const &data) #if defined(PQXX_HAVE_CONCEPTS) -template -concept char_sized = (sizeof(CHAR) == 1); +template concept char_sized = (sizeof(CHAR) == 1); # define PQXX_CHAR_SIZED_ARG char_sized #else # define PQXX_CHAR_SIZED_ARG typename diff --git a/include/pqxx/zview.hxx b/include/pqxx/zview.hxx index be7e8ccd2..07172cff8 100644 --- a/include/pqxx/zview.hxx +++ b/include/pqxx/zview.hxx @@ -118,9 +118,9 @@ namespace pqxx::internal * support each of these individually. */ template -concept ZString = std::is_convertible_v < strip_t, -char const * > or std::is_convertible_v, zview> or - std::is_convertible_v; +concept ZString = std::is_convertible_v, char const *> or + std::is_convertible_v, zview> or + std::is_convertible_v; } // namespace pqxx::internal #endif // PQXX_HAVE_CONCEPTS diff --git a/src/Makefile.in b/src/Makefile.in index 920243c37..9b6c3270c 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.16.3 from Makefile.am. +# Makefile.in generated by automake 1.16.2 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. diff --git a/src/connection.cxx b/src/connection.cxx index 29f83148c..8752636fb 100644 --- a/src/connection.cxx +++ b/src/connection.cxx @@ -108,13 +108,43 @@ pqxx::connection::connection(connection &&rhs) : } +pqxx::connection::connection( + connection::connect_mode, zview connection_string) : + m_conn{PQconnectStart(connection_string.c_str())} +{ + if (m_conn == nullptr) + throw std::bad_alloc{}; +} + + +std::pair pqxx::connection::poll_connect() +{ + switch (PQconnectPoll(m_conn)) + { + case PGRES_POLLING_FAILED: + throw pqxx::broken_connection{PQerrorMessage(m_conn)}; + case PGRES_POLLING_READING: return std::make_pair(true, false); + case PGRES_POLLING_WRITING: return std::make_pair(false, true); + case PGRES_POLLING_OK: + if (not is_open()) + throw pqxx::broken_connection{PQerrorMessage(m_conn)}; + return std::make_pair(false, false); + case PGRES_POLLING_ACTIVE: + throw internal_error{ + "Nonblocking connection poll returned obsolete 'active' state."}; + default: + throw internal_error{ + "Nonblocking connection poll returned unknown value."}; + } +} + void pqxx::connection::complete_init() { if (m_conn == nullptr) throw std::bad_alloc{}; try { - if (PQstatus(m_conn) != CONNECTION_OK) + if (not is_open()) throw broken_connection{PQerrorMessage(m_conn)}; set_up_state(); @@ -955,93 +985,55 @@ pqxx::connection::esc_like(std::string_view text, char escape_char) const namespace { -// Convert a timeval to milliseconds, or -1 if no timeval is given. -[[maybe_unused]] constexpr int tv_milliseconds(timeval *tv = nullptr) +unsigned to_milli(std::time_t seconds, long microseconds) { - if (tv == nullptr) - return -1; - else - return pqxx::check_cast( - tv->tv_sec * 1000 + tv->tv_usec / 1000, "milliseconds"sv); + return pqxx::check_cast( + (seconds * 1000) + (microseconds / 1000), + "Wait timeout value out of bounds."); } +} // namespace /// Wait for an fd to become free for reading/writing. Optional timeout. -void wait_fd(int fd, bool forwrite = false, timeval *tv = nullptr) +void pqxx::internal::wait_fd( + int fd, bool for_read, bool for_write, unsigned seconds, + unsigned microseconds) { - if (fd < 0) - PQXX_UNLIKELY - throw pqxx::broken_connection{"No connection."}; - // WSAPoll is available in winsock2.h only for versions of Windows >= 0x0600 #if defined(_WIN32) && (_WIN32_WINNT >= 0x0600) - short const events{static_cast(forwrite ? POLLWRNORM : POLLRDNORM)}; + short const events{static_cast( + (for_read ? POLLRDNORM : 0) | (for_write ? POLLWRNORM : 0))}; WSAPOLLFD fdarray{SOCKET(fd), events, 0}; - WSAPoll(&fdarray, 1, tv_milliseconds(tv)); + WSAPoll(&fdarray, 1, to_milli(seconds, microseconds)); // TODO: Check for errors. #elif defined(PQXX_HAVE_POLL) auto const events{static_cast( - POLLERR | POLLHUP | POLLNVAL | (forwrite ? POLLOUT : POLLIN))}; + POLLERR | POLLHUP | POLLNVAL | (for_read ? POLLIN : 0) | + (for_write ? POLLOUT : 0))}; pollfd pfd{fd, events, 0}; - poll(&pfd, 1, tv_milliseconds(tv)); + poll(&pfd, 1, to_milli(seconds, microseconds)); // TODO: Check for errors. #else // No poll()? Our last option is select(). fd_set read_fds; FD_ZERO(&read_fds); - if (not forwrite) + if (for_read) FD_SET(fd, &read_fds); fd_set write_fds; FD_ZERO(&write_fds); - if (forwrite) + if (for_write) FD_SET(fd, &write_fds); fd_set except_fds; FD_ZERO(&except_fds); FD_SET(fd, &except_fds); - select(fd + 1, &read_fds, &write_fds, &except_fds, tv); + timeval tv = {seconds, microseconds}; + select(fd + 1, &read_fds, &write_fds, &except_fds, &tv); // TODO: Check for errors. #endif } -} // namespace - -void pqxx::internal::wait_read(internal::pq::PGconn const *c) -{ - wait_fd(socket_of(c)); -} - - -void pqxx::internal::wait_read( - internal::pq::PGconn const *c, std::time_t seconds, long microseconds) -{ - // Not all platforms have suseconds_t for the microseconds. And some 64-bit - // systems use 32-bit integers here. - timeval tv{ - check_cast(seconds, "read timeout seconds"sv), - check_cast( - microseconds, "read timeout microseconds"sv)}; - wait_fd(socket_of(c), false, &tv); -} - - -void pqxx::internal::wait_write(internal::pq::PGconn const *c) -{ - wait_fd(socket_of(c), true); -} - - -void pqxx::connection::wait_read() const -{ - internal::wait_read(m_conn); -} - - -void pqxx::connection::wait_read(std::time_t seconds, long microseconds) const -{ - internal::wait_read(m_conn, seconds, microseconds); -} int pqxx::connection::await_notification() @@ -1050,7 +1042,7 @@ int pqxx::connection::await_notification() if (notifs == 0) { PQXX_LIKELY - wait_read(); + internal::wait_fd(socket_of(m_conn), true, false, 10, 0); notifs = get_notifs(); } return notifs; @@ -1064,7 +1056,7 @@ int pqxx::connection::await_notification( if (notifs == 0) { PQXX_LIKELY - wait_read(seconds, microseconds); + internal::wait_fd(socket_of(m_conn), true, false, seconds, microseconds); return get_notifs(); } return notifs; @@ -1205,3 +1197,13 @@ std::string pqxx::connection::connection_string() const } return buf; } + + +pqxx::connection pqxx::connecting::produce() +{ + if (!done()) + throw usage_error{ + "Tried to produce a nonblocking connection before it was done."}; + m_conn.complete_init(); + return std::move(m_conn); +} diff --git a/test/Makefile.am b/test/Makefile.am index ecb264463..ed78e6d13 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -94,6 +94,7 @@ runner_SOURCES = \ unit/test_field.cxx \ unit/test_float.cxx \ unit/test_largeobject.cxx \ + unit/test_nonblocking_connect.cxx \ unit/test_notification.cxx \ unit/test_pipeline.cxx \ unit/test_prepared_statement.cxx \ diff --git a/test/Makefile.in b/test/Makefile.in index b86537bf9..7b09d3fbd 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.16.3 from Makefile.am. +# Makefile.in generated by automake 1.16.2 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. @@ -146,6 +146,7 @@ am_runner_OBJECTS = test00.$(OBJEXT) test01.$(OBJEXT) test02.$(OBJEXT) \ unit/test_errorhandler.$(OBJEXT) unit/test_escape.$(OBJEXT) \ unit/test_exceptions.$(OBJEXT) unit/test_field.$(OBJEXT) \ unit/test_float.$(OBJEXT) unit/test_largeobject.$(OBJEXT) \ + unit/test_nonblocking_connect.$(OBJEXT) \ unit/test_notification.$(OBJEXT) unit/test_pipeline.$(OBJEXT) \ unit/test_prepared_statement.$(OBJEXT) \ unit/test_read_transaction.$(OBJEXT) \ @@ -220,6 +221,7 @@ am__depfiles_remade = ./$(DEPDIR)/runner.Po ./$(DEPDIR)/test00.Po \ unit/$(DEPDIR)/test_exceptions.Po unit/$(DEPDIR)/test_field.Po \ unit/$(DEPDIR)/test_float.Po \ unit/$(DEPDIR)/test_largeobject.Po \ + unit/$(DEPDIR)/test_nonblocking_connect.Po \ unit/$(DEPDIR)/test_notification.Po \ unit/$(DEPDIR)/test_pipeline.Po \ unit/$(DEPDIR)/test_prepared_statement.Po \ @@ -522,6 +524,7 @@ runner_SOURCES = \ unit/test_field.cxx \ unit/test_float.cxx \ unit/test_largeobject.cxx \ + unit/test_nonblocking_connect.cxx \ unit/test_notification.cxx \ unit/test_pipeline.cxx \ unit/test_prepared_statement.cxx \ @@ -629,6 +632,8 @@ unit/test_float.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test_largeobject.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) +unit/test_nonblocking_connect.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) unit/test_notification.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test_pipeline.$(OBJEXT): unit/$(am__dirstamp) \ @@ -747,6 +752,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_field.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_float.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_largeobject.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_nonblocking_connect.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_notification.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_pipeline.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_prepared_statement.Po@am__quote@ # am--include-marker @@ -1090,6 +1096,7 @@ distclean: distclean-am -rm -f unit/$(DEPDIR)/test_field.Po -rm -f unit/$(DEPDIR)/test_float.Po -rm -f unit/$(DEPDIR)/test_largeobject.Po + -rm -f unit/$(DEPDIR)/test_nonblocking_connect.Po -rm -f unit/$(DEPDIR)/test_notification.Po -rm -f unit/$(DEPDIR)/test_pipeline.Po -rm -f unit/$(DEPDIR)/test_prepared_statement.Po @@ -1217,6 +1224,7 @@ maintainer-clean: maintainer-clean-am -rm -f unit/$(DEPDIR)/test_field.Po -rm -f unit/$(DEPDIR)/test_float.Po -rm -f unit/$(DEPDIR)/test_largeobject.Po + -rm -f unit/$(DEPDIR)/test_nonblocking_connect.Po -rm -f unit/$(DEPDIR)/test_notification.Po -rm -f unit/$(DEPDIR)/test_pipeline.Po -rm -f unit/$(DEPDIR)/test_prepared_statement.Po diff --git a/test/unit/test_connection.cxx b/test/unit/test_connection.cxx index 86810f0ff..e9c9cf713 100644 --- a/test/unit/test_connection.cxx +++ b/test/unit/test_connection.cxx @@ -114,8 +114,8 @@ std::size_t length(char const str[]) template void test_params_type() { #if defined(PQXX_HAVE_CONCEPTS) - using item_t = std::remove_reference_t< - decltype(*std::declval>())>; + using item_t = std::remove_reference_t>())>; using key_t = decltype(std::get<0>(std::declval())); using value_t = decltype(std::get<1>(std::declval())); diff --git a/test/unit/test_nonblocking_connect.cxx b/test/unit/test_nonblocking_connect.cxx new file mode 100644 index 000000000..31fec816a --- /dev/null +++ b/test/unit/test_nonblocking_connect.cxx @@ -0,0 +1,26 @@ +#include +#include + +#include "../test_helpers.hxx" + + +namespace +{ +void test_nonblocking_connect() +{ + pqxx::connecting nbc; + while (not nbc.done()) + { + pqxx::internal::wait_fd( + nbc.sock(), nbc.wait_to_read(), nbc.wait_to_write()); + nbc.process(); + } + + pqxx::connection conn{nbc.produce()}; + pqxx::work tx{conn}; + PQXX_CHECK_EQUAL(tx.query_value("SELECT 10"), 10, "Bad value!?"); +} + + +PQXX_REGISTER_TEST(test_nonblocking_connect); +} // namespace diff --git a/tools/Makefile.in b/tools/Makefile.in index 4457f2676..841a20a56 100644 --- a/tools/Makefile.in +++ b/tools/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.16.3 from Makefile.am. +# Makefile.in generated by automake 1.16.2 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc.