Author: brane
Date: Fri Jul 25 14:25:43 2025
New Revision: 1927469

Log:
On the user-defined-authn branch: sync with trunk r1927468.

Modified:
   serf/branches/user-defined-authn/   (props changed)
   serf/branches/user-defined-authn/CMakeLists.txt
   serf/branches/user-defined-authn/README
   serf/branches/user-defined-authn/buckets/ssl_buckets.c
   serf/branches/user-defined-authn/build/FindAPR.cmake
   serf/branches/user-defined-authn/build/FindAPRUtil.cmake
   serf/branches/user-defined-authn/build/SerfGenClangd.cmake
   serf/branches/user-defined-authn/serf.h
   serf/branches/user-defined-authn/serf_private.h
   serf/branches/user-defined-authn/src/context.c
   serf/branches/user-defined-authn/src/outgoing.c
   serf/branches/user-defined-authn/src/resolve.c
   serf/branches/user-defined-authn/test/MockHTTPinC/CMakeLists.txt
   serf/branches/user-defined-authn/test/test_context.c
   serf/branches/user-defined-authn/test/test_ssl.c
   serf/branches/user-defined-authn/test/test_util.c

Modified: serf/branches/user-defined-authn/CMakeLists.txt
==============================================================================
--- serf/branches/user-defined-authn/CMakeLists.txt     Fri Jul 25 13:56:10 
2025        (r1927468)
+++ serf/branches/user-defined-authn/CMakeLists.txt     Fri Jul 25 14:25:43 
2025        (r1927469)
@@ -28,6 +28,7 @@
 # ZLIB_ROOT         - Path to zlib's install area
 # Brotli_ROOT       - Path to Brotli's install area
 # GSSAPI_ROOT       - Path to GSSAPI's install area
+# Unbound_ROOT      - Path to libunbound's install area
 # ===================================================================
 
 cmake_minimum_required(VERSION 3.12)
@@ -263,6 +264,21 @@ else()
   endif()
 endif()
 
+
+# FIXME: VERYTEMPORARY, figure out FindUnbound.cmake first.
+set(Unbound_FOUND FALSE)
+if (Unbound_FOUND)
+  set(UNBOUND_INCLUDE_DIR "/opt/homebrew/opt/unbound/include")
+  set(UNBOUND_LIBRARIES "/opt/homebrew/opt/unbound/lib/libunbound.dylib")
+  # set(UNBOUND_LIBRARIES "/usr/lib/aarch64-linux-gnu/libunbound.so")
+  # set(UNBOUND_LIBRARIES "/usr/lib64/libunbound.so")
+  add_library(Unbound::Unbound UNKNOWN IMPORTED)
+  set_target_properties(Unbound::Unbound PROPERTIES
+    INTERFACE_INCLUDE_DIRECTORIES "${UNBOUND_INCLUDE_DIR}")
+  set_target_properties(Unbound::Unbound PROPERTIES
+    IMPORTED_LOCATION "${UNBOUND_LIBRARIES}")
+endif(Unbound_FOUND)
+
 # Calculate the set of private and public targets
 set(SERF_PRIVATE_TARGETS OpenSSL::Crypto OpenSSL::SSL ZLIB::ZLIB)
 if(Brotli_FOUND)
@@ -272,6 +288,11 @@ if(GSSAPI_FOUND)
   list(APPEND SERF_C_DEFINES "SERF_HAVE_GSSAPI")
   list(APPEND SERF_PRIVATE_TARGETS KRB5::GSSAPI)
 endif()
+if(Unbound_FOUND)
+  list(APPEND SERF_C_DEFINES "SERF_HAVE_ASYNC_RESOLVER=1")
+  list(APPEND SERF_C_DEFINES "SERF_HAVE_UNBOUND")
+  list(APPEND SERF_PRIVATE_TARGETS Unbound::Unbound)
+endif()
 
 if(APR_STATIC)
   if(SERF_WINDOWS)
@@ -497,6 +518,8 @@ if(NOT SERF_WINDOWS)
       ${APRUTIL_LDFLAGS}
       ${APRUTIL_LIBRARIES}
       ${APRUTIL_EXTRALIBS}
+      ${BROTLI_COMMON_LIBRARY}
+      ${BROTLI_DECODE_LIBRARY}
       ${GSSAPI_LIBRARIES}
       ${ZLIB_LIBRARIES}
       )
@@ -532,6 +555,7 @@ set(_gen_dot_clangd OFF)
 set(_have_brotli OFF)
 set(_have_gssapi OFF)
 set(_have_sspi OFF)
+set(_have_unbound OFF)
 
 if(NOT SKIP_SHARED)
   set(_build_shared ON)
@@ -558,6 +582,9 @@ endif()
 if("SERF_HAVE_SSPI" IN_LIST SERF_C_DEFINES)
   set(_have_sspi ON)
 endif()
+if ("SERF_HAVE_UNBOUND" IN_LIST SERF_C_DEFINES)
+  set(_have_unbound "EXPERIMENTAL")
+endif()
 
 message(STATUS "Summary:")
 message(STATUS "  Version ................... : ${SERF_VERSION}")
@@ -574,6 +601,7 @@ message(STATUS "  Options:")
 message(STATUS "    Brotli .................. : ${_have_brotli}")
 message(STATUS "    GSSAPI .................. : ${_have_gssapi}")
 message(STATUS "    SSPI .................... : ${_have_sspi}")
+message(STATUS "    Unbound ................. : ${_have_unbound}")
 message(STATUS "  Install:")
 message(STATUS "    prefix: ................. : ${CMAKE_INSTALL_PREFIX}")
 message(STATUS "    headers: ................ : ${SERF_INSTALL_HEADERS}")

Modified: serf/branches/user-defined-authn/README
==============================================================================
--- serf/branches/user-defined-authn/README     Fri Jul 25 13:56:10 2025        
(r1927468)
+++ serf/branches/user-defined-authn/README     Fri Jul 25 14:25:43 2025        
(r1927469)
@@ -31,7 +31,7 @@ create a symlink for 'scons' in your PAT
 
 Fetch the scons-local package:
   http://prdownloads.sourceforge.net/scons/scons-local-2.3.5.tar.gz
-  
+
 Alternatively create a virtual Python environment and install SCons via
 pip (replace the path ~/scons_venv with your preferred path):
 
@@ -105,6 +105,11 @@ $ scons --help
 
 $ scons check
 
+Some tests take a long time to run, for example a 4GB request. These can
+be enabled by passing ENABLE_SLOW_TESTS on the command line:
+
+$ scons check ENABLE_SLOW_TESTS=1
+
 
 1.1.4 Installing Apache Serf
 
@@ -236,6 +241,11 @@ or, on Windows using the Visual Studio g
     $ cmake --build out --config Release --target run_tests
 )
 
+Some tests take a long time to run, for example a 4GB request. These can
+be enabled by passing ENABLE_SLOW_TESTS on the command line:
+
+$ cmake -DENABLE_SLOW_TESTS=ON
+
 
 1.2.5 Installing Apache Serf
 

Modified: serf/branches/user-defined-authn/buckets/ssl_buckets.c
==============================================================================
--- serf/branches/user-defined-authn/buckets/ssl_buckets.c      Fri Jul 25 
13:56:10 2025        (r1927468)
+++ serf/branches/user-defined-authn/buckets/ssl_buckets.c      Fri Jul 25 
14:25:43 2025        (r1927469)
@@ -1608,7 +1608,6 @@ static int ssl_pass_cb(UI *ui, UI_STRING
 static int ssl_need_client_cert(SSL *ssl, X509 **cert, EVP_PKEY **pkey)
 {
     serf_ssl_context_t *ctx = SSL_get_app_data(ssl);
-    unsigned long err = 0;
 #if defined(SERF_HAVE_OSSL_STORE_OPEN_EX)
     STACK_OF(X509) *leaves;
     STACK_OF(X509) *intermediates;
@@ -1919,7 +1918,7 @@ static int ssl_need_client_cert(SSL *ssl
             return 1;
         }
         else {
-            err = ERR_get_error();
+            unsigned long err = ERR_get_error();
             ERR_clear_error();
             if (ERR_GET_LIB(err) == ERR_LIB_PKCS12 &&
                 ERR_GET_REASON(err) == PKCS12_R_MAC_VERIFY_FAILURE) {

Modified: serf/branches/user-defined-authn/build/FindAPR.cmake
==============================================================================
--- serf/branches/user-defined-authn/build/FindAPR.cmake        Fri Jul 25 
13:56:10 2025        (r1927468)
+++ serf/branches/user-defined-authn/build/FindAPR.cmake        Fri Jul 25 
14:25:43 2025        (r1927469)
@@ -42,7 +42,7 @@ cmake_minimum_required(VERSION 3.12)
 #   APR_FOUND          - True if APR was found.
 #   APR_VERSION        - The version of APR found (x.y.z)
 #   APR_CONTAINS_APRUTIL - True if the APR major version is 2 or greater.
-#   APR_INCLUDES       - Where to find apr.h, etc.
+#   APR_INCLUDE_DIR    - Where to find apr.h, etc.
 #   APR_LIBRARIES      - Linker switches to use with ld to link against APR
 #
 # ::
@@ -211,7 +211,7 @@ if(NOT _apru_include_only_utilities)
 
   if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
 
-    _apru_find_win_version("apr" APR_INCLUDES
+    _apru_find_win_version("apr" APR_INCLUDE_DIR
                            APR_VERSION _apr_major _apr_minor)
     set(_apr_name "apr-${_apr_major}")
 
@@ -229,12 +229,11 @@ if(NOT _apru_include_only_utilities)
       _apru_config(${APR_CONFIG_EXECUTABLE} ${_varname} "${_regexp}" "${ARGN}")
     endmacro(_apr_invoke)
 
-    _apr_invoke(APR_CFLAGS     "(^| )-[gOW][^ ]*" --cppflags --cflags)
-    _apr_invoke(APR_INCLUDES   "(^| )-I"          --includes)
-    _apr_invoke(APR_LDFLAGS    ""                 --ldflags)
-    _apr_invoke(APR_LIBRARIES  ""                 --link-ld)
-    _apr_invoke(APR_EXTRALIBS  ""                 --libs)
-    _apr_invoke(APR_VERSION    ""                 --version)
+    _apr_invoke(APR_CFLAGS      "(^| )-[gOW][^ ]*" --cppflags --cflags)
+    _apr_invoke(APR_INCLUDE_DIR ""                 --includedir)
+    _apr_invoke(APR_LIBRARIES   ""                 --link-ld)
+    _apr_invoke(APR_EXTRALIBS   ""                 --libs)
+    _apr_invoke(APR_VERSION     ""                 --version)
     string(REGEX REPLACE "^([0-9]+)\\..*$" "\\1" _apr_major "${APR_VERSION}")
 
   endif()   # NOT Windows
@@ -248,7 +247,7 @@ if(NOT _apru_include_only_utilities)
   include(FindPackageHandleStandardArgs)
   find_package_handle_standard_args(
     APR
-    REQUIRED_VARS APR_LIBRARIES APR_INCLUDES
+    REQUIRED_VARS APR_LIBRARIES APR_INCLUDE_DIR
     VERSION_VAR APR_VERSION)
 
   if(APR_FOUND)
@@ -257,7 +256,7 @@ if(NOT _apru_include_only_utilities)
       if(APR_LIBRARIES AND APR_RUNTIME_LIBS)
         add_library(APR::APR SHARED IMPORTED)
         set_target_properties(APR::APR PROPERTIES
-          INTERFACE_INCLUDE_DIRECTORIES "${APR_INCLUDES}"
+          INTERFACE_INCLUDE_DIRECTORIES "${APR_INCLUDE_DIR}"
           IMPORTED_LOCATION "${APR_RUNTIME_LIBS}"
           IMPORTED_IMPLIB "${APR_LIBRARIES}")
       endif()
@@ -267,9 +266,10 @@ if(NOT _apru_include_only_utilities)
         add_library(APR::APR_static STATIC IMPORTED)
         set_target_properties(APR::APR_static PROPERTIES
           INTERFACE_COMPILE_DEFINITIONS "APR_DECLARE_STATIC"
-          INTERFACE_INCLUDE_DIRECTORIES "${APR_INCLUDES}"
-          IMPORTED_INTERFACE_LINK_LIBRARIES "${_apr_extra}"
+          INTERFACE_INCLUDE_DIRECTORIES "${APR_INCLUDE_DIR}"
           IMPORTED_LOCATION "${_apr_static}")
+        target_link_libraries(APR::APR_static
+          INTERFACE ${_apr_extra})
       endif()
 
     else()    # NOT Windows
@@ -277,9 +277,10 @@ if(NOT _apru_include_only_utilities)
       _apru_location(_apr_library _apr_extra "${APR_LIBRARIES}")
       add_library(APR::APR UNKNOWN IMPORTED)
       set_target_properties(APR::APR PROPERTIES
-        INTERFACE_INCLUDE_DIRECTORIES "${APR_INCLUDES}"
-        INTERFACE_LINK_LIBRARIES 
"${APR_LDFLAGS};${APR_EXTRALIBS};${_apr_extra}"
+        INTERFACE_INCLUDE_DIRECTORIES "${APR_INCLUDE_DIR}"
         IMPORTED_LOCATION "${_apr_library}")
+      target_link_libraries(APR::APR
+        INTERFACE ${APR_EXTRALIBS} ${_apr_extra})
 
     endif()   # NOT Windows
   endif(APR_FOUND)

Modified: serf/branches/user-defined-authn/build/FindAPRUtil.cmake
==============================================================================
--- serf/branches/user-defined-authn/build/FindAPRUtil.cmake    Fri Jul 25 
13:56:10 2025        (r1927468)
+++ serf/branches/user-defined-authn/build/FindAPRUtil.cmake    Fri Jul 25 
14:25:43 2025        (r1927469)
@@ -41,7 +41,7 @@ cmake_minimum_required(VERSION 3.12)
 #
 #   APRUtil_FOUND          - True if APR-Util was found
 #   APRUTIL_VERSION        - The version of APR-Util found (x.y.z)
-#   APRUTIL_INCLUDES       - Where to find apr.h, etc.
+#   APRUTIL_INCLUDE_DIR    - Where to find apr.h, etc.
 #   APRUTIL_LIBRARIES      - Linker switches to use with ld to link against APR
 #
 # ::
@@ -83,7 +83,7 @@ else(APR_CONTAINS_APRUTIL)
 
   if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
 
-    _apru_find_win_version("apu" APRUTIL_INCLUDES
+    _apru_find_win_version("apu" APRUTIL_INCLUDE_DIR
                            APRUTIL_VERSION _apu_major _apu_minor)
     set(_apu_name "aprutil-${_apu_major}")
 
@@ -122,18 +122,17 @@ else(APR_CONTAINS_APRUTIL)
       _apru_config(${APRUTIL_CONFIG_EXECUTABLE} ${_varname} "${_regexp}" 
"${ARGN}")
     endmacro(_apu_invoke)
 
-    _apu_invoke(APRUTIL_INCLUDES  "(^| )-I" --includes)
-    _apu_invoke(APRUTIL_EXTRALIBS ""        --libs)
-    _apu_invoke(APRUTIL_LIBRARIES ""        --link-ld)
-    _apu_invoke(APRUTIL_LDFLAGS   ""        --ldflags)
-    _apu_invoke(APRUTIL_VERSION   ""        --version)
+    _apu_invoke(APRUTIL_INCLUDE_DIR  ""  --includedir)
+    _apu_invoke(APRUTIL_EXTRALIBS    ""  --libs)
+    _apu_invoke(APRUTIL_LIBRARIES    ""  --link-ld)
+    _apu_invoke(APRUTIL_VERSION      ""  --version)
 
   endif()   # NOT Windows
 
   include(FindPackageHandleStandardArgs)
   find_package_handle_standard_args(
     APRUtil
-    REQUIRED_VARS APRUTIL_LIBRARIES APRUTIL_INCLUDES
+    REQUIRED_VARS APRUTIL_LIBRARIES APRUTIL_INCLUDE_DIR
     VERSION_VAR APRUTIL_VERSION)
 
   if(APRUtil_FOUND)
@@ -142,7 +141,7 @@ else(APR_CONTAINS_APRUTIL)
       if(APRUTIL_LIBRARIES AND APRUTIL_RUNTIME_LIBS)
         add_library(APR::APRUtil SHARED IMPORTED)
         set_target_properties(APR::APRUtil PROPERTIES
-          INTERFACE_INCLUDE_DIRECTORIES "${APRUTIL_INCLUDES}"
+          INTERFACE_INCLUDE_DIRECTORIES "${APRUTIL_INCLUDE_DIR}"
           IMPORTED_LOCATION "${APRUTIL_RUNTIME_LIBS}"
           IMPORTED_IMPLIB "${APRUTIL_LIBRARIES}")
         if(TARGET APR::APR)
@@ -159,7 +158,7 @@ else(APR_CONTAINS_APRUTIL)
         add_library(APR::APRUtil_static STATIC IMPORTED)
         set_target_properties(APR::APRUtil_static PROPERTIES
           INTERFACE_COMPILE_DEFINITIONS "APU_DECLARE_STATIC"
-          INTERFACE_INCLUDE_DIRECTORIES "${APRUTIL_INCLUDES}"
+          INTERFACE_INCLUDE_DIRECTORIES "${APRUTIL_INCLUDE_DIR}"
           IMPORTED_LOCATION "${_apu_static}")
         target_link_libraries(APR::APRUtil_static
           INTERFACE ${_apu_extra})
@@ -170,10 +169,10 @@ else(APR_CONTAINS_APRUTIL)
       _apru_location(_apu_library _apu_extra "${APRUTIL_LIBRARIES}")
       add_library(APR::APRUtil UNKNOWN IMPORTED)
       set_target_properties(APR::APRUtil PROPERTIES
-        INTERFACE_INCLUDE_DIRECTORIES "${APRUTIL_INCLUDES}"
+        INTERFACE_INCLUDE_DIRECTORIES "${APRUTIL_INCLUDE_DIR}"
         IMPORTED_LOCATION "${_apu_library}")
       target_link_libraries(APR::APRUtil
-          INTERFACE ${APRUTIL_LDFLAGS};${APRUTIL_EXTRALIBS};${_apu_extra})
+        INTERFACE ${APRUTIL_EXTRALIBS} ${_apu_extra})
     endif()   # NOT Windows
   endif(APRUtil_FOUND)
 

Modified: serf/branches/user-defined-authn/build/SerfGenClangd.cmake
==============================================================================
--- serf/branches/user-defined-authn/build/SerfGenClangd.cmake  Fri Jul 25 
13:56:10 2025        (r1927468)
+++ serf/branches/user-defined-authn/build/SerfGenClangd.cmake  Fri Jul 25 
14:25:43 2025        (r1927469)
@@ -55,9 +55,9 @@ function(SerfGenClangd)
   write_flags("--language=c")
   write_includes("${CMAKE_SOURCE_DIR}")
 
-  list(APPEND includes ${APR_INCLUDES})
+  list(APPEND includes ${APR_INCLUDE_DIR})
   if(NOT APR_CONTAINS_APRUTIL)
-    list(APPEND includes ${APRUTIL_INCLUDES})
+    list(APPEND includes ${APRUTIL_INCLUDE_DIR})
   endif()
   list(APPEND includes ${OPENSSL_INCLUDE_DIR})
   list(APPEND includes ${ZLIB_INCLUDE_DIR})
@@ -67,6 +67,9 @@ function(SerfGenClangd)
   if(GSSAPI_FOUND)
     list(APPEND includes ${GSSAPI_INCLUDES})
   endif()
+  if(Unbound_FOUND)
+    list(APPEND includes ${UNBOUND_INCLUDE_DIR})
+  endif()
   list(REMOVE_DUPLICATES includes)
   write_includes(${includes})
 

Modified: serf/branches/user-defined-authn/serf.h
==============================================================================
--- serf/branches/user-defined-authn/serf.h     Fri Jul 25 13:56:10 2025        
(r1927468)
+++ serf/branches/user-defined-authn/serf.h     Fri Jul 25 14:25:43 2025        
(r1927469)
@@ -551,7 +551,7 @@ apr_status_t serf_connection_create3(
  * The @a ctx and @a resolved_baton arguments are the same that were passed
  * to serf_address_resolve_async().
  *
- * @a status contains the result of the address resolution. If it is notably
+ * @a status contains the result of the address resolution. If it is not
  * @c APR_SUCCESS, then @a host_address is invalid and should be ignored.
  *
  * The resolved @a host_address is ephemeral, allocated iun @a pool and lives
@@ -597,6 +597,56 @@ apr_status_t serf_address_resolve_async(
     apr_pool_t *pool);
 
 
+/**
+ * Notification callback when a connection hae been created.
+ *
+ * The @a ctx and @a created_baton arguments are the same that were passed
+ * to serf_connection_create_async().
+ *
+ * @a status contains the result of the connection creation. If it is not
+ * @c APR_SUCCESS, then @a conn is invalid and should be ignored.
+ *
+ * The created @a conn is allocated in the pool that was passed to
+ * serf_connection_create_async(); this is @b not the same as @a pool.
+ *
+ * All temporary allocations should be made in @a pool.
+ *
+ * @since New in 1.4.
+ */
+/* FIXME: EXPERIMENTAL */
+typedef void (*serf_connection_created_t)(
+    serf_context_t *ctx,
+    void *created_baton,
+    serf_connection_t *conn,
+    apr_status_t status,
+    apr_pool_t *pool);
+
+/**
+ * Asyncchronously create a new connection associated with
+ * the @a ctx serf context.
+ *
+ * Like serf_connection_create3() with @a host_address set to @c NULL,
+ * except that address resolution is performed asynchronously, similarly to
+ * serf_address_resolve_async().
+ *
+ * The @a created callback with @a created_baton is called when the connection
+ * is created but before it is opened.
+ *
+ * @since New in 1.4.
+ */
+/* FIXME: EXPERIMENTAL */
+apr_status_t serf_connection_create_async(
+    serf_context_t *ctx,
+    apr_uri_t host_info,
+    serf_connection_created_t created,
+    void *created_baton,
+    serf_connection_setup_t setup,
+    void *setup_baton,
+    serf_connection_closed_t closed,
+    void *closed_baton,
+    apr_pool_t *pool);
+
+
 typedef apr_status_t (*serf_accept_client_t)(
     serf_context_t *ctx,
     serf_listener_t *l,

Modified: serf/branches/user-defined-authn/serf_private.h
==============================================================================
--- serf/branches/user-defined-authn/serf_private.h     Fri Jul 25 13:56:10 
2025        (r1927468)
+++ serf/branches/user-defined-authn/serf_private.h     Fri Jul 25 14:25:43 
2025        (r1927469)
@@ -440,17 +440,6 @@ serf__config_store_remove_host(serf__con
                                const char *hostname_port);
 
 
-typedef struct serf__resolve_result_t serf__resolve_result_t;
-struct serf__resolve_result_t
-{
-    apr_sockaddr_t *host_address;
-    apr_status_t status;
-    serf_address_resolved_t resolved;
-    void *resolved_baton;
-    apr_pool_t *result_pool;
-    serf__resolve_result_t *next;
-};
-
 struct serf_context_t {
     /* the pool used for self and for other allocations */
     apr_pool_t *pool;
@@ -498,11 +487,10 @@ struct serf_context_t {
 
     serf_config_t *config;
 
-    /* The results of asynchronous address resolution. */
-#if APR_HAS_THREADS
-    apr_thread_mutex_t *resolve_guard;
-#endif
-    serf__resolve_result_t *resolve_head;
+    /* Support for asynchronous address resolution. */
+    void *volatile resolve_head;
+    apr_status_t resolve_init_status;
+    void *resolve_context;
 };
 
 struct serf_listener_t {
@@ -684,6 +672,10 @@ struct serf_connection_t {
    up buckets that may still reference buckets of this request */
 void serf__connection_pre_cleanup(serf_connection_t *);
 
+/* Called from serf_context_create_ex() to set up the context-specific
+   asynchronous address resolver context. */
+apr_status_t serf__create_resolve_context(serf_context_t *ctx);
+
 /* Called from serf_context_prerun() before handling the connections.
    Processes the results of any asynchronously resolved addresses
    that were initiated for CTX. */

Modified: serf/branches/user-defined-authn/src/context.c
==============================================================================
--- serf/branches/user-defined-authn/src/context.c      Fri Jul 25 13:56:10 
2025        (r1927468)
+++ serf/branches/user-defined-authn/src/context.c      Fri Jul 25 14:25:43 
2025        (r1927469)
@@ -136,6 +136,20 @@ void serf_config_authn_types(serf_contex
 }
 
 
+#ifdef BROKEN_WSAPOLL
+/* APR 1.4.x switched to using WSAPoll() on Win32, but it does not
+ * properly handle errors on a non-blocking sockets (such as
+ * connecting to a server where no listener is active).
+ *
+ * So, sadly, we must force using select() on Win32.
+ *
+ * 
http://mail-archives.apache.org/mod_mbox/apr-dev/201105.mbox/%3cbanlktin3rbceccbrvzua5b-14u-nwxr...@mail.gmail.com%3E
+ */
+#define PLATFORM_POLLSET_METHOD APR_POLLSET_SELECT
+#else
+#define PLATFORM_POLLSET_METHOD APR_POLLSET_DEFAULT
+#endif
+
 serf_context_t *serf_context_create_ex(
     void *user_baton,
     serf_socket_add_t addf,
@@ -160,20 +174,8 @@ serf_context_t *serf_context_create_ex(
            ### Probably move creation of the pollset to later when we have
            ### the possibility of returning status to the caller.
          */
-#ifdef BROKEN_WSAPOLL
-        /* APR 1.4.x switched to using WSAPoll() on Win32, but it does not
-         * properly handle errors on a non-blocking sockets (such as
-         * connecting to a server where no listener is active).
-         *
-         * So, sadly, we must force using select() on Win32.
-         *
-         * 
http://mail-archives.apache.org/mod_mbox/apr-dev/201105.mbox/%3cbanlktin3rbceccbrvzua5b-14u-nwxr...@mail.gmail.com%3E
-         */
         (void) apr_pollset_create_ex(&ps->pollset, MAX_CONN, pool, 0,
-                                     APR_POLLSET_SELECT);
-#else
-        (void) apr_pollset_create(&ps->pollset, MAX_CONN, pool, 0);
-#endif
+                                     PLATFORM_POLLSET_METHOD);
         ctx->pollset_baton = ps;
         ctx->pollset_add = pollset_add;
         ctx->pollset_rm = pollset_rm;
@@ -193,13 +195,12 @@ serf_context_t *serf_context_create_ex(
     ctx->server_authn_info = apr_hash_make(pool);
 
     /* Initialize async resolver result queue. */
-#if APR_HAS_THREADS
-    /* FIXME: Ignore the status? */
-    apr_thread_mutex_create(&ctx->resolve_guard,
-                            APR_THREAD_MUTEX_DEFAULT,
-                            ctx->pool);
-#endif
     ctx->resolve_head = NULL;
+    ctx->resolve_init_status = APR_SUCCESS;
+    ctx->resolve_init_status = serf__create_resolve_context(ctx);
+    if (ctx->resolve_init_status != APR_SUCCESS) {
+        ctx->resolve_context = NULL;
+    }
 
     /* Assume returned status is APR_SUCCESS */
     serf__config_store_init(ctx);

Modified: serf/branches/user-defined-authn/src/outgoing.c
==============================================================================
--- serf/branches/user-defined-authn/src/outgoing.c     Fri Jul 25 13:56:10 
2025        (r1927468)
+++ serf/branches/user-defined-authn/src/outgoing.c     Fri Jul 25 14:25:43 
2025        (r1927469)
@@ -1362,6 +1362,78 @@ apr_status_t serf_connection_create3(
     return status;
 }
 
+
+struct async_create_baton
+{
+    apr_uri_t host_info;
+    serf_connection_created_t created;
+    void *created_baton;
+    serf_connection_setup_t setup;
+    void *setup_baton;
+    serf_connection_closed_t closed;
+    void *closed_baton;
+    apr_pool_t *conn_pool;
+};
+
+static void async_conn_create(serf_context_t *ctx,
+                              void *resolved_baton,
+                              apr_sockaddr_t *host_address,
+                              apr_status_t status,
+                              apr_pool_t *scratch_pool)
+{
+    struct async_create_baton *const baton = resolved_baton;
+    serf_connection_t *conn = NULL;
+
+    if (status == APR_SUCCESS)
+    {
+        status = apr_sockaddr_info_copy(&host_address, host_address,
+                                        baton->conn_pool);
+        if (status == APR_SUCCESS) {
+            status = serf_connection_create3(&conn, ctx,
+                                             baton->host_info,
+                                             host_address,
+                                             baton->setup, baton->setup_baton,
+                                             baton->closed, 
baton->closed_baton,
+                                             baton->conn_pool);
+        }
+    }
+
+    baton->created(ctx, baton->created_baton, conn, status, scratch_pool);
+}
+
+apr_status_t serf_connection_create_async(
+    serf_context_t *ctx,
+    apr_uri_t host_info,
+    serf_connection_created_t created,
+    void *created_baton,
+    serf_connection_setup_t setup,
+    void *setup_baton,
+    serf_connection_closed_t closed,
+    void *closed_baton,
+    apr_pool_t *pool)
+{
+    apr_pool_t *scratch_pool;
+    apr_status_t status;
+
+    struct async_create_baton *const baton = apr_palloc(pool, sizeof(*baton));
+    baton->host_info = host_info;
+    baton->created = created;
+    baton->created_baton = created_baton;
+    baton->setup = setup;
+    baton->setup_baton = setup_baton;
+    baton->closed = closed;
+    baton->closed_baton = closed_baton;
+    baton->conn_pool = pool;
+
+    apr_pool_create(&scratch_pool, pool);
+    status = serf_address_resolve_async(ctx, host_info,
+                                        async_conn_create, baton,
+                                        scratch_pool);
+    apr_pool_destroy(scratch_pool);
+    return status;
+}
+
+
 apr_status_t serf_connection_reset(
     serf_connection_t *conn)
 {

Modified: serf/branches/user-defined-authn/src/resolve.c
==============================================================================
--- serf/branches/user-defined-authn/src/resolve.c      Fri Jul 25 13:56:10 
2025        (r1927468)
+++ serf/branches/user-defined-authn/src/resolve.c      Fri Jul 25 14:25:43 
2025        (r1927469)
@@ -19,29 +19,49 @@
  */
 
 #include <apr.h>
+#include <apr_version.h>
+
+/* Include the headers needed for inet_ntop and related structs.
+   On Windows, we'll always get <Winsock2.h> from <apr.h>. */
+#if APR_HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#if APR_HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
 #include <apr_errno.h>
-#include <apr_network_io.h>
 #include <apr_pools.h>
+#include <apr_atomic.h>
+#include <apr_network_io.h>
 #include <apr_thread_mutex.h>
 #include <apr_thread_pool.h>
 
+/* Third-party resolver headers. */
+#if SERF_HAVE_ASYNC_RESOLVER
+#if SERF_HAVE_UNBOUND
+#include <unbound.h>
+#else
+/* Really shouldn't happen, but just in case it does, fall back
+   to the apr_thread_pool-based resolver. */
+#undef SERF_HAVE_ASYNC_RESOLVER
+#endif  /* SERF_HAVE_UNBOUND */
+#endif
+
 #include "serf.h"
 #include "serf_private.h"
 
 
-#define HAVE_ASYNC_RESOLVER (SERF_USE_ASYNC_RESOLVER || APR_HAS_THREADS)
+#define HAVE_ASYNC_RESOLVER (SERF_HAVE_ASYNC_RESOLVER || APR_HAS_THREADS)
 
 /*
  * FIXME: EXPERIMENTAL
  * TODO:
- *  - Add cleanup function for in-flight resolve tasks if their owning
- *    context is destroyed. This function should be called from the
- *    context's pool cleanup handler.
- *  - Figure out what to do if the lock/unlock calls return an error.
- *    This should not be possible unless we messed up the implementation,
- *    but there should be a way for clients to back out of this situation.
- *    Failed lock/unlock could potentially leave the context in an
- *    inconsistent state.
+ *  - Wake the poll/select in serf_context_run() when new resolve
+ *    results are available.
+ *
+ * TODO for Unbound:
+ *  - Convert unbound results to apr_sockaddr_t.
  */
 
 
@@ -51,7 +71,7 @@
    onto the context's result queue. */
 static void push_resolve_result(serf_context_t *ctx,
                                 apr_sockaddr_t *host_address,
-                                apr_status_t status,
+                                apr_status_t resolve_status,
                                 serf_address_resolved_t resolved,
                                 void *resolved_baton,
                                 apr_pool_t *resolve_pool);
@@ -73,6 +93,10 @@ apr_status_t serf_address_resolve_async(
 {
     apr_pool_t *resolve_pool;
 
+    if (ctx->resolve_init_status != APR_SUCCESS) {
+        return ctx->resolve_init_status;
+    }
+
     apr_pool_create(&resolve_pool, ctx->pool);
 
     /* See serf_connection_create3(): if there's a proxy configured in the
@@ -105,10 +129,16 @@ apr_status_t serf_address_resolve_async(
 #endif  /* !HAVE_ASYNC_RESOLVER */
 
 
-#if SERF_USE_ASYNC_RESOLVER
+#if SERF_HAVE_ASYNC_RESOLVER
 
 /* TODO: Add implementation for one or more async resolver libraries. */
 #if 0
+/* Called during context creation. Must initialize ctx->resolver_context. */
+static apr_status_t create_resolve_context(serf_context_t *ctx)
+{
+    ...
+}
+
 static apr_status_t resolve_address_async(serf_context_t *ctx,
                                           apr_uri_t host_info,
                                           serf_address_resolved_t resolved,
@@ -122,13 +152,360 @@ static apr_status_t resolve_address_asyn
 /* Some asynchronous resolved libraries use event loop to harvest results.
    This function will be called from serf__process_async_resolve_results()
    so, in effect, from serf_context_prerun(). */
-static void run_async_resolver_loop(void)
+static apr_status_t run_async_resolver_loop(serf_context_t *ctx)
 {
     ...
 }
+#endif  /* 0 */
+
+#if SERF_HAVE_UNBOUND
+
+/* DNS classes and record types.
+   https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml */
+#define RR_CLASS_IN    1        /* Internet */
+#define RR_TYPE_A      1        /* IPv4 address */
+#define RR_TYPE_AAAA  28        /* IPv6 address */
+
+
+static apr_status_t err_to_status(enum ub_ctx_err err)
+{
+    switch (err)
+    {
+    case UB_NOERROR:
+        /* no error */
+        return APR_SUCCESS;
+
+    case UB_SOCKET:
+        /* socket operation. Set to -1, so that if an error from _fd() is
+           passed (-1) it gives a socket error. */
+        if (errno)
+            return APR_FROM_OS_ERROR(errno);
+        return APR_ENOTSOCK;
+
+    case UB_NOMEM:
+        /* alloc failure */
+        return APR_ENOMEM;
+
+    case UB_SYNTAX:
+        /* syntax error */
+        return APR_EINIT;
+
+    case UB_SERVFAIL:
+        /* DNS service failed */
+        return APR_EAGAIN;
+
+    case UB_FORKFAIL:
+        /* fork() failed */
+        return APR_ENOMEM;
+
+    case UB_AFTERFINAL:
+        /* cfg change after finalize() */
+        return APR_EINIT;
+
+    case UB_INITFAIL:
+        /* initialization failed (bad settings) */
+        return APR_EINIT;
+
+    case UB_PIPE:
+        /* error in pipe communication with async bg worker */
+        return APR_EPIPE;
+
+    case UB_READFILE:
+        /* error reading from file (resolv.conf) */
+        if (errno)
+            return APR_FROM_OS_ERROR(errno);
+        return APR_ENOENT;
+
+    case UB_NOID:
+        /* error async_id does not exist or result already been delivered */
+        return APR_EINVAL;
+
+    default:
+        return APR_EGENERAL;
+    }
+}
+
+
+struct resolve_context
+{
+    struct ub_ctx *ub_ctx;
+    volatile apr_uint32_t tasks;
+};
+
+static apr_status_t cleanup_resolve_context(void *baton)
+{
+    struct resolve_context *const rctx = baton;
+    ub_ctx_delete(rctx->ub_ctx);
+    return APR_SUCCESS;
+}
+
+static apr_status_t create_resolve_context(serf_context_t *ctx)
+{
+    struct resolve_context *const rctx = apr_palloc(ctx->pool, sizeof(*rctx));
+    int err;
+
+    rctx->ub_ctx = ub_ctx_create();
+    rctx->tasks = 0;
+    if (!rctx->ub_ctx)
+        return APR_ENOMEM;
+
+    err = ub_ctx_resolvconf(rctx->ub_ctx, NULL);
+    if (!err)
+        err = ub_ctx_hosts(rctx->ub_ctx, NULL);
+    if (!err)
+        err = ub_ctx_async(rctx->ub_ctx, true);
+
+    if (err) {
+        const apr_status_t status = err_to_status(err);
+        /* TODO: Error callback */
+        serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, ctx->config,
+                  "unbound ctx init: %s\n", ub_strerror(err));
+        cleanup_resolve_context(rctx);
+        return status;
+    }
+
+    ctx->resolve_context = rctx;
+    /* pre-cleanup because the live resolve tasks contain subpools of the
+       context pool and must be canceled before their pools go away. */
+    apr_pool_pre_cleanup_register(ctx->pool, rctx, cleanup_resolve_context);
+    return APR_SUCCESS;
+}
+
+
+/* Task data for the Unbound resolver. */
+typedef struct unbound_resolve_task resolve_task_t;
+
+struct resolve_result
+{
+    int err;
+    apr_status_t status;
+    struct ub_result* ub_result;
+    resolve_task_t *task;
+    const char *qtype;
+};
+
+struct unbound_resolve_task
+{
+    serf_context_t *ctx;
+    apr_port_t host_port;
+
+    /* There can be one or two pending results, depending on whether
+       we resolve for IPv6 as well as IPv4. */
+    volatile apr_uint32_t pending_results;
+    struct resolve_result results[2];
+
+    serf_address_resolved_t resolved;
+    void *resolved_baton;
+    apr_pool_t *resolve_pool;
+};
+
+static void resolve_finalize(resolve_task_t *task)
+{
+    /* TODO: Convert ub_result to apr_sockaddr_t */
+    if (task->results[0].ub_result)
+        ub_resolve_free(task->results[0].ub_result);
+    if (task->results[1].ub_result)
+        ub_resolve_free(task->results[1].ub_result);
+
+    push_resolve_result(task->ctx, NULL, APR_EAFNOSUPPORT,
+                        task->resolved, task->resolved_baton,
+                        task->resolve_pool);
+}
+
+static void resolve_callback(void* baton, int err,
+                             struct ub_result* ub_result)
+{
+    struct resolve_result *const resolve_result = baton;
+    resolve_task_t *const task = resolve_result->task;
+    apr_status_t status = err_to_status(err);
+
+    struct resolve_context *const rctx = task->ctx->resolve_context;
+    apr_atomic_dec32(&rctx->tasks);
+
+    if (err)
+    {
+        /* TODO: Error callback */
+        serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, task->ctx->config,
+                  "unbound resolve: [%s] error %s\n",
+                  resolve_result->qtype, ub_strerror(err));
+    }
+    else if (!ub_result->havedata)
+    {
+        if (ub_result->nxdomain) {
+            /* TODO: Error callback */
+            serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, task->ctx->config,
+                      "unbound resolve: [%s] NXDOMAIN [%d]\n",
+                      resolve_result->qtype, ub_result->rcode);
+            if (status == APR_SUCCESS)
+                status = APR_ENOENT;
+        }
+        if (ub_result->bogus) {
+            /* TODO: Error callback */
+            serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, task->ctx->config,
+                      "unbound resolve: [%s] BOGUS [%d]%s%s\n",
+                      resolve_result->qtype, ub_result->rcode,
+                      ub_result->why_bogus ? " " : "",
+                      ub_result->why_bogus ? ub_result->why_bogus : "");
+            if (status == APR_SUCCESS)
+                status = APR_EINVAL;
+        }
+        if (ub_result->was_ratelimited) {
+            /* TODO: Error callback */
+            serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, task->ctx->config,
+                      "unbound resolve: [%s] SERVFAIL [%d]\n",
+                      resolve_result->qtype, ub_result->rcode);
+            if (status == APR_SUCCESS)
+                status = APR_EAGAIN;
+        }
+
+        /* This shouldn't happen, one of the previous checks should
+           have caught an error. */
+        if (status == APR_SUCCESS) {
+            /* TODO: Error callback */
+            serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, task->ctx->config,
+                      "unbound resolve: [%s] no data [%d]\n",
+                      resolve_result->qtype, ub_result->rcode);
+            status = APR_ENOENT;
+        }
+    }
+
+    resolve_result->err = err;
+    resolve_result->status = status;
+    resolve_result->ub_result = ub_result;
+
+    if (status == APR_SUCCESS
+        && serf__log_enabled(LOGLVL_DEBUG, LOGCOMP_CONN, task->ctx->config))
+    {
+        char buf[INET6_ADDRSTRLEN];
+        const socklen_t len = sizeof(buf);
+        int i;
+
+        for (i = 0; ub_result->data && ub_result->data[i]; ++i) {
+            const char *address = "(AF-unknown)";
+
+            if (ub_result->len[i] == sizeof(struct in_addr))
+                address = inet_ntop(AF_INET, ub_result->data[i], buf, len);
+            else if (ub_result->len[i] == sizeof(struct in6_addr))
+                address = inet_ntop(AF_INET6, ub_result->data[i], buf, len);
+            serf__log(LOGLVL_DEBUG, LOGCOMP_CONN,
+                      __FILE__, task->ctx->config,
+                      "unbound resolve: [%s] %s: %s\n",
+                      resolve_result->qtype, ub_result->qname, address);
+        }
+    }
+
+    /* The last pending task combines and publishes the results. */
+    if (apr_atomic_dec32(&task->pending_results) == 1)
+        resolve_finalize(task);
+}
+
+static apr_status_t resolve_address_async(serf_context_t *ctx,
+                                          apr_uri_t host_info,
+                                          serf_address_resolved_t resolved,
+                                          void *resolved_baton,
+                                          apr_pool_t *resolve_pool,
+                                          apr_pool_t *scratch_pool)
+{
+    struct resolve_context *const rctx = ctx->resolve_context;
+    resolve_task_t *const task = apr_palloc(resolve_pool, sizeof(*task));
+    apr_status_t status = APR_SUCCESS;
+    int err4 = 0, err6 = 0;
+
+    task->ctx = ctx;
+    task->host_port = host_info.port;
+
+#if APR_HAVE_IPV6
+    task->pending_results = 2;
+#else
+    task->pending_results = 1;
 #endif
+    task->results[0].err = task->results[1].err = 0;
+    task->results[0].status = task->results[1].status = APR_SUCCESS;
+    task->results[0].ub_result = task->results[1].ub_result = NULL;
+    task->results[0].task = task->results[1].task = task;
+    task->results[0].qtype = task->results[1].qtype = "??";
+
+    task->resolved = resolved;
+    task->resolved_baton = resolved_baton;
+    task->resolve_pool = resolve_pool;
+
+    task->results[0].qtype = "v4";
+    err4 = ub_resolve_async(rctx->ub_ctx, host_info.hostname,
+                            RR_TYPE_A, RR_CLASS_IN,
+                            &task->results[0], resolve_callback, NULL);
+    if (!err4) {
+        apr_atomic_inc32(&rctx->tasks);
+    }
+
+#if APR_HAVE_IPV6
+    task->results[1].qtype = "v6";
+    err6 = ub_resolve_async(rctx->ub_ctx, host_info.hostname,
+                            RR_TYPE_AAAA, RR_CLASS_IN,
+                            &task->results[1], resolve_callback, NULL);
+    if (!err6) {
+        apr_atomic_inc32(&rctx->tasks);
+    }
+#endif  /* APR_HAVE_IPV6 */
+
+    if (err4 || err6)
+    {
+        apr_uint32_t pending_results = -1;
 
-#else    /* !SERF_USE_ASYNC_RESOLVER */
+        if (err4) {
+            pending_results = apr_atomic_dec32(&task->pending_results);
+            /* TODO: Error callback */
+            serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, ctx->config,
+                      "unbound resolve start: [v4] %s\n", ub_strerror(err4));
+            status = err_to_status(err4);
+        }
+
+#if APR_HAVE_IPV6
+        if (err6) {
+            pending_results = apr_atomic_dec32(&task->pending_results);
+            /* TODO: Error callback */
+            serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, ctx->config,
+                      "unbound resolve start: [v6] %s\n", ub_strerror(err6));
+            /* We have only one status to report. */
+            if (!err4)
+                status = err_to_status(err6);
+        }
+#endif  /* APR_HAVE_IPV6 */
+
+        /* If one of the tasks failed and the other has already completed,
+           we have to do the result processing here. Note that the Unbound
+           callbacks can be called synchronously from ub_resolve_async(). */
+        if (pending_results == 1)
+            resolve_finalize(task);
+    }
+
+    return status;
+}
+
+static apr_status_t run_async_resolver_loop(serf_context_t *ctx)
+{
+    struct resolve_context *const rctx = ctx->resolve_context;
+
+    /* No need to poll if there are no in-flight tasks. */
+    if (apr_atomic_read32(&rctx->tasks))
+    {
+        if (ub_poll(rctx->ub_ctx)) {
+            const int err = ub_process(rctx->ub_ctx);
+            if (err) {
+                const apr_status_t status = err_to_status(err);
+                /* TODO: Error callback */
+                serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, ctx->config,
+                          "unbound process: %s\n", ub_strerror(err));
+                return status;
+            }
+        }
+    }
+
+    return APR_SUCCESS;
+}
+
+#endif  /* SERF_HAVE_UNBOUND */
+
+#else   /* !SERF_HAVE_ASYNC_RESOLVER */
 #if APR_HAS_THREADS
 
 /* This could be made configurable, but given that this is a fallback
@@ -136,9 +513,10 @@ static void run_async_resolver_loop(void
 #define MAX_WORK_QUEUE_THREADS 50
 static apr_pool_t *work_pool = NULL;
 static apr_thread_pool_t *work_queue = NULL;
-static apr_status_t init_work_queue(void *baton)
+
+static apr_status_t do_init_work_queue(void *baton)
 {
-    serf_context_t *ctx = baton;
+    serf_context_t *const ctx = baton;
     apr_status_t status;
 
     apr_pool_create(&work_pool, NULL);
@@ -152,10 +530,35 @@ static apr_status_t init_work_queue(void
     return status;
 }
 
+static apr_status_t init_work_queue(serf_context_t *ctx)
+{
+    SERF__DECLARE_STATIC_INIT_ONCE_CONTEXT(init_ctx);
+    return serf__init_once(&init_ctx, do_init_work_queue, ctx);
+}
+
+
+static apr_status_t cleanup_resolve_tasks(void *baton)
+{
+    /* baton is serf_context_t */
+    return apr_thread_pool_tasks_cancel(work_queue, baton);
+}
+
+static apr_status_t create_resolve_context(serf_context_t *ctx)
+{
+    apr_status_t status;
+
+    ctx->resolve_context = NULL;
+    status = init_work_queue(ctx);
+    if (status == APR_SUCCESS)
+        apr_pool_pre_cleanup_register(ctx->pool, ctx, cleanup_resolve_tasks);
+
+    return status;
+}
+
 
 /* Task data for the thred pool resolver. */
-typedef struct resolve_task_t resolve_task_t;
-struct resolve_task_t
+typedef struct threadpool_resolve_task resolve_task_t;
+struct threadpool_resolve_task
 {
     serf_context_t *ctx;
     apr_uri_t host_info;
@@ -176,6 +579,28 @@ static void *APR_THREAD_FUNC resolve(apr
                                    APR_UNSPEC,
                                    task->host_info.port,
                                    0, task->resolve_pool);
+
+    if (status) {
+        host_address = NULL;
+    }
+    else if (serf__log_enabled(LOGLVL_DEBUG, LOGCOMP_CONN, task->ctx->config))
+    {
+        apr_sockaddr_t *addr = host_address;
+        while (addr)
+        {
+            char buf[INET6_ADDRSTRLEN];
+            const socklen_t len = sizeof(buf);
+            const char *address = "(AF-unknown)";
+
+            if (addr->family == APR_INET || addr->family == APR_INET6)
+                address = inet_ntop(addr->family, addr->ipaddr_ptr, buf, len);
+            serf__log(LOGLVL_DEBUG, LOGCOMP_CONN,
+                      __FILE__, task->ctx->config,
+                      "apr async resolve: %s: %s\n", addr->hostname, address);
+            addr = addr->next;
+        }
+    }
+
     push_resolve_result(task->ctx, host_address, status,
                         task->resolved, task->resolved_baton,
                         task->resolve_pool);
@@ -190,10 +615,7 @@ static apr_status_t resolve_address_asyn
                                           apr_pool_t *scratch_pool)
 {
     resolve_task_t *task;
-    apr_status_t status;
-    SERF__DECLARE_STATIC_INIT_ONCE_CONTEXT(init_ctx);
-
-    status = serf__init_once(&init_ctx, init_work_queue, ctx);
+    apr_status_t status = init_work_queue(ctx);
     if (status)
         return status;
 
@@ -210,103 +632,93 @@ static apr_status_t resolve_address_asyn
 
 /* This is a no-op since we're using a thread pool that
    does its own task queue management. */
-static void run_async_resolver_loop(void) {}
+static apr_status_t run_async_resolver_loop(serf_context_t *ctx)
+{
+    return APR_SUCCESS;
+}
 
 #endif  /* !APR_HAS_THREADS */
-#endif  /* !SERF_USE_ASYNC_RESOLVER */
+#endif  /* !SERF_HAVE_ASYNC_RESOLVER */
 
 
 /*******************************************************************/
 /* The result queue implementation. */
 #if HAVE_ASYNC_RESOLVER
 
-static apr_status_t lock_results(serf_context_t *ctx)
-{
-#if APR_HAS_THREADS
-    apr_status_t status = apr_thread_mutex_lock(ctx->resolve_guard);
-    if (status) {
-        /* TODO: ctx->error_callback... */
-        char buffer[256];
-        serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, ctx->config,
-                  "Lock async resolve results: %s\n",
-                  apr_strerror(status, buffer, sizeof(buffer)));
-    }
-    return status;
-#else
-    return APR_SUCCESS;
-#endif
-}
+#if APR_MAJOR_VERSION < 2
+/* NOTE: The atomic pointer prototypes in apr-1.x are just horribly
+         wrong. They're fixed in version 2.x and these macros deal
+         with the difference. */
+#define apr_atomic_casptr(mem, with, cmp) \
+    (apr_atomic_casptr)((volatile void**)(mem), (with), (cmp))
+#define apr_atomic_xchgptr(mem, with) \
+    (apr_atomic_xchgptr)((volatile void**)(mem), (with))
+#endif  /* APR_MAJOR_VERSION < 2 */
+
 
-static apr_status_t unlock_results(serf_context_t *ctx)
+typedef struct resolve_result_t resolve_result_t;
+struct resolve_result_t
 {
-#if APR_HAS_THREADS
-    apr_status_t status = apr_thread_mutex_unlock(ctx->resolve_guard);
-    if (status) {
-        /* TODO: ctx->error_callback... */
-        char buffer[256];
-        serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, ctx->config,
-                  "Unlock async resolve results: %s\n",
-                  apr_strerror(status, buffer, sizeof(buffer)));
-    }
-    return status;
-#else
-    return APR_SUCCESS;
-#endif
-}
+    apr_sockaddr_t *host_address;
+    apr_status_t status;
+    serf_address_resolved_t resolved;
+    void *resolved_baton;
+    apr_pool_t *result_pool;
+    resolve_result_t *next;
+};
 
 
 static void push_resolve_result(serf_context_t *ctx,
                                 apr_sockaddr_t *host_address,
-                                apr_status_t status,
+                                apr_status_t resolve_status,
                                 serf_address_resolved_t resolved,
                                 void *resolved_baton,
                                 apr_pool_t *resolve_pool)
 {
-    serf__resolve_result_t *result;
-    apr_status_t lock_status;
+    resolve_result_t *result;
+    void *head;
 
     result = apr_palloc(resolve_pool, sizeof(*result));
     result->host_address = host_address;
-    result->status = status;
+    result->status = resolve_status;
     result->resolved = resolved;
     result->resolved_baton = resolved_baton;
     result->result_pool = resolve_pool;
 
-    lock_status = lock_results(ctx);
-    if (!lock_status)
-    {
-        result->next = ctx->resolve_head;
-        ctx->resolve_head = result;
-        lock_status = unlock_results(ctx);
-    }
+    /* Atomic push this result to the result stack. This might look like
+       a potential priority inversion, however, it's not likely that we'll
+       resolve several tens of thousands of results per second in hundreds
+       of separate threads. */
+    head = apr_atomic_casptr(&ctx->resolve_head, NULL, NULL);
+    do {
+        result->next = head;
+        head = apr_atomic_casptr(&ctx->resolve_head, result, head);
+    } while(head != result->next);
+}
+
 
-    /* TODO: if (lock_status) ... then what? */
+/* Internal API */
+apr_status_t serf__create_resolve_context(serf_context_t *ctx)
+{
+    return create_resolve_context(ctx);
 }
 
 
 /* Internal API */
 apr_status_t serf__process_async_resolve_results(serf_context_t *ctx)
 {
-    serf__resolve_result_t *result = NULL;
-    apr_status_t lock_status;
-
-    run_async_resolver_loop();
+    resolve_result_t *result;
+    apr_status_t status;
 
-    lock_status = lock_results(ctx);
-    if (lock_status)
-        return lock_status;
-
-    result = ctx->resolve_head;
-    ctx->resolve_head = NULL;
-    lock_status = unlock_results(ctx);
-
-    /* TODO: if (lock_status) ... then what? Shouldn't be possible. */
-    /* if (lock_status) */
-    /*     return lock_status; */
+    status = run_async_resolver_loop(ctx);
+    if (status)
+        return status;
 
+    /* Grab the whole stack, leaving it empty, and process the contents. */
+    result = apr_atomic_xchgptr(&ctx->resolve_head, NULL);
     while (result)
     {
-        serf__resolve_result_t *const next = result->next;
+        resolve_result_t *const next = result->next;
         result->resolved(ctx, result->resolved_baton,
                          result->host_address, result->status,
                          result->result_pool);

Modified: serf/branches/user-defined-authn/test/MockHTTPinC/CMakeLists.txt
==============================================================================
--- serf/branches/user-defined-authn/test/MockHTTPinC/CMakeLists.txt    Fri Jul 
25 13:56:10 2025        (r1927468)
+++ serf/branches/user-defined-authn/test/MockHTTPinC/CMakeLists.txt    Fri Jul 
25 14:25:43 2025        (r1927469)
@@ -56,5 +56,7 @@ target_compile_options(mockhttpinc
 target_compile_definitions(mockhttpinc
                            PUBLIC "MOCKHTTP_OPENSSL"
                            PRIVATE ${MockHTTPinC_DEFINES})
-target_include_directories(mockhttpinc SYSTEM BEFORE
-                           PRIVATE ${APR_INCLUDES} ${APRUTIL_INCLUDES})
+target_link_libraries(mockhttpinc
+                      ${SERF_PRIVATE_TARGETS}
+                      ${SERF_PUBLIC_TARGETS}
+                      ${SERF_STANDARD_LIBRARIES})

Modified: serf/branches/user-defined-authn/test/test_context.c
==============================================================================
--- serf/branches/user-defined-authn/test/test_context.c        Fri Jul 25 
13:56:10 2025        (r1927468)
+++ serf/branches/user-defined-authn/test/test_context.c        Fri Jul 25 
14:25:43 2025        (r1927469)
@@ -1046,8 +1046,8 @@ static void test_async_resolve(CuTest *t
         if (!APR_STATUS_IS_TIMEUP(status))
             CuAssertIntEquals(tc, APR_SUCCESS, status);
     }
-    CuAssertPtrNotNull(tc, tb->connection);
     CuAssertIntEquals(tc, APR_SUCCESS, tb->user_status);
+    CuAssertPtrNotNull(tc, tb->connection);
 
     /* Send some requests on the connections */
     for (i = 0 ; i < num_requests ; i++) {
@@ -1058,6 +1058,44 @@ static void test_async_resolve(CuTest *t
                                                 handler_ctx, tb->pool);
 }
 
+static void async_resolve_cancel_callback(serf_context_t *ctx,
+                                          void *resolved_baton,
+                                          apr_sockaddr_t *host_address,
+                                          apr_status_t status,
+                                          apr_pool_t *scratch_pool)
+{
+    *(int*)resolved_baton = 1;
+}
+
+static void test_async_resolve_cancel(CuTest *tc)
+{
+    test_baton_t *tb = tc->testBaton;
+    serf_context_t *ctx;
+    apr_pool_t *ctx_pool;
+    apr_status_t status;
+    apr_uri_t url;
+    int resolved = 0;
+
+    status = apr_uri_parse(tb->pool, "http://localhost:8080/";, &url);
+    CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+    apr_pool_create(&ctx_pool, tb->pool);
+    CuAssertPtrNotNull(tc, ctx_pool);
+    ctx = serf_context_create(ctx_pool);
+    CuAssertPtrNotNull(tc, ctx);
+
+    status = serf_address_resolve_async(ctx, url,
+                                        async_resolve_cancel_callback,
+                                        &resolved, ctx_pool);
+
+    /* This would create and actual race in the test case: */
+    /* serf_context_prerun(ctx); */
+
+    apr_pool_destroy(ctx_pool);
+    CuAssertIntEquals(tc, APR_SUCCESS, status);
+    CuAssertIntEquals(tc, 0, resolved);
+}
+
 
 /*****************************************************************************/
 CuSuite *test_context(void)
@@ -1086,5 +1124,6 @@ CuSuite *test_context(void)
     SUITE_ADD_TEST(suite, test_max_keepalive_requests);
     SUITE_ADD_TEST(suite, test_outgoing_request_err);
     SUITE_ADD_TEST(suite, test_async_resolve);
+    SUITE_ADD_TEST(suite, test_async_resolve_cancel);
     return suite;
 }

Modified: serf/branches/user-defined-authn/test/test_ssl.c
==============================================================================
--- serf/branches/user-defined-authn/test/test_ssl.c    Fri Jul 25 13:56:10 
2025        (r1927468)
+++ serf/branches/user-defined-authn/test/test_ssl.c    Fri Jul 25 14:25:43 
2025        (r1927469)
@@ -31,6 +31,7 @@
 
 #include <openssl/ssl.h>
 #include <openssl/x509v3.h>
+#include <openssl/opensslv.h>
 #ifndef OPENSSL_NO_OCSP /* requires openssl 0.9.7 or later */
 #include <openssl/ocsp.h>
 #endif
@@ -2783,10 +2784,16 @@ static void test_ssl_ocsp_verify_respons
 {
 #ifndef OPENSSL_NO_OCSP
     apr_status_t status = verify_ocsp_response(tc, 1, 0, 0, 0);
-    /* OCSP responses MUST be signed, we can't even create one
-       without a signature. This error doesn't come from response
-       validation but because OCSP_response_create() fails. */
+#if OPENSSL_VERSION_NUMBER >= (3 << 28) /* OpenSSL 3.0.0 */
+    /* OCSP responses MUST be signed, and on newer versions of OpenSSL we
+       can't even create one without a signature. This error doesn't come
+       from response validation but because OCSP_response_create() fails. */
     CuAssertIntEquals(tc, APR_EGENERAL, status);
+#else
+    /* But both LibreSSL and OpenSSL up to 1.1.1 do allow creating such
+       a response, and so our validation will return a different error. */
+    CuAssertIntEquals(tc, SERF_ERROR_SSL_OCSP_RESPONSE_INVALID, status);
+#endif
 #endif  /* OPENSSL_NO_OCSP */
 }
 

Modified: serf/branches/user-defined-authn/test/test_util.c
==============================================================================
--- serf/branches/user-defined-authn/test/test_util.c   Fri Jul 25 13:56:10 
2025        (r1927468)
+++ serf/branches/user-defined-authn/test/test_util.c   Fri Jul 25 14:25:43 
2025        (r1927469)
@@ -142,44 +142,22 @@ apr_status_t use_new_connection(test_bat
     return status;
 }
 
-struct async_reolved_baton
+static void conn_created(serf_context_t *ctx,
+                         void *resolved_baton,
+                         serf_connection_t *conn,
+                         apr_status_t status,
+                         apr_pool_t *unused_scratch_pool)
 {
-    test_baton_t *tb;
-    apr_uri_t url;
-};
-
-static void address_resolved(serf_context_t *ctx,
-                             void *resolved_baton,
-                             apr_sockaddr_t *host_address,
-                             apr_status_t status,
-                             apr_pool_t *unused_scratch_pool)
-{
-    struct async_reolved_baton *baton = resolved_baton;
-    test_baton_t *tb = baton->tb;
-    serf_connection_t *conn;
-    apr_pool_t *conn_pool = tb->pool;
+    test_baton_t *tb = resolved_baton;
 
     if (tb->context != ctx)
         REPORT_TEST_SUITE_ERROR();
 
     if (status == APR_SUCCESS)
     {
-        status = apr_sockaddr_info_copy(&host_address, host_address, 
conn_pool);
-        if (status == APR_SUCCESS)
-            status = serf_connection_create3(&conn, ctx,
-                                             baton->url,
-                                             host_address,
-                                             tb->conn_setup,
-                                             tb,
-                                             default_closed_connection,
-                                             tb,
-                                             conn_pool);
-        if (status == APR_SUCCESS)
-        {
-            tb->connection = conn;
-            apr_pool_cleanup_register(conn_pool, tb->connection, cleanup_conn,
-                                      apr_pool_cleanup_null);
-        }
+        tb->connection = conn;
+        apr_pool_cleanup_register(tb->pool, tb->connection, cleanup_conn,
+                                  apr_pool_cleanup_null);
     }
 
     tb->user_status = status;
@@ -190,7 +168,6 @@ apr_status_t use_new_async_connection(te
 {
     apr_uri_t url;
     apr_status_t status;
-    struct async_reolved_baton *baton;
 
     if (tb->connection)
         cleanup_conn(tb->connection);
@@ -200,12 +177,12 @@ apr_status_t use_new_async_connection(te
     if (status != APR_SUCCESS)
         return status;
 
-    baton = apr_palloc(pool, sizeof(*baton));
-    baton->tb = tb;
-    baton->url = url;
     tb->user_status = APR_SUCCESS;
-    return serf_address_resolve_async(tb->context, url,
-                                      address_resolved, baton, pool);
+    return serf_connection_create_async(tb->context, url,
+                                        conn_created, tb,
+                                        tb->conn_setup, tb,
+                                        default_closed_connection, tb,
+                                        tb->pool);
 }
 
 static test_baton_t *initTestCtx(CuTest *tc, apr_pool_t *pool)

Reply via email to