Author: brane
Date: Fri Jul 25 02:22:06 2025
New Revision: 1927456

Log:
Get rid of the mutex that serializes access to the async resolver results.
They're a singly-linked stack, so use lockless push and clear instead.

* serf_private.h
  (serf_context_t): Make the resolve_head volatile, and just a void*.
   Remove resolve_guard and all the #if thread stuff.
* src/context.c
  (serf_context_create_ex): Simplify the initialization for async resolving.

* src/resolve.c: Include <apr_version.h>. Update the top-level todo list.
  (lock_results, unlock_results): Remove.
  (apr_atomic_casptr, apr_atomic_xchgptr): Add wrapper macros for APR-1.x,
   because the prototypes in that version are wrong.
  (push_resolve_result): Use a lockless push to the result stack.
  (serf__process_async_resolve_results): No locking, just a pointer exchange.

Modified:
   serf/trunk/serf_private.h
   serf/trunk/src/context.c
   serf/trunk/src/resolve.c

Modified: serf/trunk/serf_private.h
==============================================================================
--- serf/trunk/serf_private.h   Fri Jul 25 01:26:09 2025        (r1927455)
+++ serf/trunk/serf_private.h   Fri Jul 25 02:22:06 2025        (r1927456)
@@ -499,11 +499,8 @@ struct serf_context_t {
     serf_config_t *config;
 
     /* Support for asynchronous address resolution. */
+    void *volatile resolve_head;
     apr_status_t resolve_init_status;
-    serf__resolve_result_t *resolve_head;
-#if APR_HAS_THREADS
-    apr_thread_mutex_t *resolve_guard;
-#endif
     void *resolve_context;
 };
 

Modified: serf/trunk/src/context.c
==============================================================================
--- serf/trunk/src/context.c    Fri Jul 25 01:26:09 2025        (r1927455)
+++ serf/trunk/src/context.c    Fri Jul 25 02:22:06 2025        (r1927456)
@@ -195,18 +195,9 @@ serf_context_t *serf_context_create_ex(
     ctx->server_authn_info = apr_hash_make(pool);
 
     /* Initialize async resolver result queue. */
-    ctx->resolve_init_status = APR_SUCCESS;
     ctx->resolve_head = NULL;
-#if APR_HAS_THREADS
-    ctx->resolve_init_status = apr_thread_mutex_create(
-        &ctx->resolve_guard, APR_THREAD_MUTEX_DEFAULT, ctx->pool);
-    if (ctx->resolve_init_status != APR_SUCCESS) {
-        ctx->resolve_guard = NULL;
-    }
-#endif
-    if (ctx->resolve_init_status == APR_SUCCESS) {
-        ctx->resolve_init_status = serf__create_resolve_context(ctx);
-    }
+    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;
     }

Modified: serf/trunk/src/resolve.c
==============================================================================
--- serf/trunk/src/resolve.c    Fri Jul 25 01:26:09 2025        (r1927455)
+++ serf/trunk/src/resolve.c    Fri Jul 25 02:22:06 2025        (r1927456)
@@ -19,6 +19,7 @@
  */
 
 #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>. */
@@ -59,12 +60,6 @@
  *  - Wake the poll/select in serf_context_run() when new resolve
  *    results are available.
  *
- *  - 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.
- *
  * TODO for Unbound:
  *  - Convert unbound results to apr_sockaddr_t.
  */
@@ -650,39 +645,15 @@ static apr_status_t run_async_resolver_l
 /* 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
-}
-
-static apr_status_t unlock_results(serf_context_t *ctx)
-{
-#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
-}
+#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 void push_resolve_result(serf_context_t *ctx,
@@ -693,7 +664,7 @@ static void push_resolve_result(serf_con
                                 apr_pool_t *resolve_pool)
 {
     serf__resolve_result_t *result;
-    apr_status_t status;
+    void *head;
 
     result = apr_palloc(resolve_pool, sizeof(*result));
     result->host_address = host_address;
@@ -702,15 +673,15 @@ static void push_resolve_result(serf_con
     result->resolved_baton = resolved_baton;
     result->result_pool = resolve_pool;
 
-    status = lock_results(ctx);
-    if (!status)
-    {
-        result->next = ctx->resolve_head;
-        ctx->resolve_head = result;
-        status = unlock_results(ctx);
-    }
-
-    /* TODO: if (status) ... then what? */
+    /* 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);
 }
 
 
@@ -724,25 +695,15 @@ apr_status_t serf__create_resolve_contex
 /* Internal API */
 apr_status_t serf__process_async_resolve_results(serf_context_t *ctx)
 {
-    serf__resolve_result_t *result = NULL;
+    serf__resolve_result_t *result;
     apr_status_t status;
 
     status = run_async_resolver_loop(ctx);
     if (status)
         return status;
 
-    status = lock_results(ctx);
-    if (status)
-        return status;
-
-    result = ctx->resolve_head;
-    ctx->resolve_head = NULL;
-    status = unlock_results(ctx);
-
-    /* TODO: if (status) ... then what? Shouldn't be possible. */
-    /* 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;

Reply via email to