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;