This is an automated email from the ASF dual-hosted git repository.

robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-c-core.git


The following commit(s) were added to refs/heads/master by this push:
     new 4c4721677 Fix async callback memory management with reference counting 
(AXIS2C-1584)
4c4721677 is described below

commit 4c47216770b4db0f6c6f53a096846764c00e246f
Author: Robert Lazarski <[email protected]>
AuthorDate: Mon Jan 12 10:12:13 2026 -1000

    Fix async callback memory management with reference counting (AXIS2C-1584)
    
    Add reference counting to axis2_callback to prevent use-after-free and
    memory leaks in async non-blocking operations. The callback is shared
    between the main thread and worker thread, requiring proper lifecycle
    management.
    
    Changes:
    - Add ref counter to axis2_callback struct, initialized to 1
    - Add axis2_callback_increment_ref() for thread-safe ref increment
    - Modify axis2_callback_free() to decrement ref and only free at zero
    - Update op_client worker thread to properly manage callback refs
    - Update svc_client async operations to balance ref counts
    - Handle NULL response in worker thread with error callback
    - Add error checking for axis2_engine_send()
    
    Co-Authored-By: Claude Opus 4.5 <[email protected]>
---
 include/axis2_callback.h        | 13 ++++++++++
 src/core/clientapi/callback.c   | 31 ++++++++++++++++++++++++
 src/core/clientapi/op_client.c  | 53 ++++++++++++++++++++++++++++-------------
 src/core/clientapi/svc_client.c | 13 +++++++++-
 4 files changed, 92 insertions(+), 18 deletions(-)

diff --git a/include/axis2_callback.h b/include/axis2_callback.h
index 84d09061f..cf4f952af 100644
--- a/include/axis2_callback.h
+++ b/include/axis2_callback.h
@@ -235,8 +235,21 @@ extern "C"
         axis2_callback_t * callback,
         axis2_on_error_func_ptr f);
 
+    /**
+     * Increments the reference count of the callback.
+     * Used in async operations where callback is shared between threads.
+     * @param callback pointer to callback struct
+     * @param env pointer to environment struct
+     * @return AXIS2_SUCCESS on success, else AXIS2_FAILURE
+     */
+    AXIS2_EXTERN axis2_status_t AXIS2_CALL
+    axis2_callback_increment_ref(
+        axis2_callback_t * callback,
+        const axutil_env_t * env);
+
     /**
      * Frees callback struct.
+     * Decrements reference count and only frees when count reaches zero.
      * @param callback pointer to callback struct
      * @param env pointer to environment struct
      * @return AXIS2_SUCCESS on success, else AXIS2_FAILURE
diff --git a/src/core/clientapi/callback.c b/src/core/clientapi/callback.c
index 6f875df1b..a926abf31 100644
--- a/src/core/clientapi/callback.c
+++ b/src/core/clientapi/callback.c
@@ -21,6 +21,8 @@
 
 struct axis2_callback
 {
+    /** reference count for preventing premature free in async operations */
+    int ref;
 
     /** callback complete? */
     axis2_bool_t complete;
@@ -105,6 +107,7 @@ axis2_callback_create(
     callback->on_error = axis2_callback_on_error;
 
     callback->mutex = axutil_thread_mutex_create(env->allocator, 
AXIS2_THREAD_MUTEX_DEFAULT);
+    callback->ref = 1;
     return callback;
 }
 
@@ -208,11 +211,39 @@ axis2_callback_set_error(
     return AXIS2_SUCCESS;
 }
 
+AXIS2_EXTERN axis2_status_t AXIS2_CALL
+axis2_callback_increment_ref(
+    axis2_callback_t * callback,
+    const axutil_env_t * env)
+{
+    axutil_thread_mutex_lock(callback->mutex);
+    callback->ref++;
+    axutil_thread_mutex_unlock(callback->mutex);
+
+    return AXIS2_SUCCESS;
+}
+
 AXIS2_EXTERN void AXIS2_CALL
 axis2_callback_free(
     axis2_callback_t * callback,
     const axutil_env_t * env)
 {
+    axutil_thread_mutex_lock(callback->mutex);
+    callback->ref--;
+
+    if(callback->ref > 0)
+    {
+        axutil_thread_mutex_unlock(callback->mutex);
+        return;
+    }
+
+    axutil_thread_mutex_unlock(callback->mutex);
+
+    if(callback->msg_ctx)
+    {
+        axis2_msg_ctx_free(callback->msg_ctx, env);
+    }
+
     if(callback->mutex)
     {
         axutil_thread_mutex_destroy(callback->mutex);
diff --git a/src/core/clientapi/op_client.c b/src/core/clientapi/op_client.c
index 4b7a90d1f..f41dab11e 100644
--- a/src/core/clientapi/op_client.c
+++ b/src/core/clientapi/op_client.c
@@ -501,7 +501,11 @@ axis2_op_client_execute(
                 "Op client execute failed due to engine creation failure.");
             return AXIS2_FAILURE;
         }
-        axis2_engine_send(engine, env, msg_ctx);
+        if(axis2_engine_send(engine, env, msg_ctx) != AXIS2_SUCCESS)
+        {
+            axis2_engine_free(engine, env);
+            return AXIS2_FAILURE;
+        }
         axis2_engine_free(engine, env);
     }
     else /* Same channel will be used irrespective of message exchange 
pattern. */
@@ -641,10 +645,10 @@ axis2_op_client_free(
     if(!op_client)
         return;
 
-    /*if(op_client->callback)
+    if(op_client->callback)
     {
         axis2_callback_free(op_client->callback, env);
-    }*/
+    }
 
     if(op_client->op_ctx)
     {
@@ -690,10 +694,12 @@ axis2_op_client_worker_func(
     }
 
     th_env = axutil_init_thread_env(args_list->env);
+    axis2_callback_increment_ref(args_list->callback, th_env);
 
     op_ctx = axis2_op_ctx_create(th_env, args_list->op, 
args_list->op_client->svc_ctx);
     if(!op_ctx)
     {
+        axis2_callback_free(args_list->callback, th_env);
         return NULL;
     }
     axis2_msg_ctx_set_op_ctx(args_list->msg_ctx, th_env, op_ctx);
@@ -702,29 +708,42 @@ axis2_op_client_worker_func(
     /* send the request and wait for response */
     response = axis2_op_client_two_way_send(th_env, args_list->msg_ctx);
 
-    /* We do not need to handle the NULL response here because this thread 
function is called only
-     * in the single channel non blocking case which, imply this is two way 
message by design.
-     */
+    /* Handle the response - NULL response indicates an error */
+    if(response)
+    {
+        /* Here after the code is a subset of what callback receiver do in 
dual channel case.*/
+        axis2_async_result_t *async_result = NULL;
 
-    /* Here after the code is a subset of what callback receiver do in dual 
channel case.*/
-    axis2_op_client_add_msg_ctx(args_list->op_client, th_env, response);
-    args_list->op_client->async_result = axis2_async_result_create(th_env, 
response);
+        axis2_op_client_add_msg_ctx(args_list->op_client, th_env, response);
+        async_result = axis2_async_result_create(th_env, response);
 
-    if(args_list->callback)
-    {
-        axis2_callback_invoke_on_complete(args_list->callback, th_env,
-            args_list->op_client->async_result);
+        if(args_list->callback)
+        {
+            axis2_callback_invoke_on_complete(args_list->callback, th_env,
+                async_result);
 
-        axis2_callback_set_complete(args_list->callback, th_env, AXIS2_TRUE);
-    }
+            axis2_callback_set_complete(args_list->callback, th_env, 
AXIS2_TRUE);
+        }
 
-    /* Clean up memory */
-    axis2_async_result_free(args_list->op_client->async_result, th_env);
+        /* Clean up memory */
+        axis2_async_result_free(async_result, th_env);
+    }
+    else
+    {
+        /* Report error to callback when response is NULL */
+        if(args_list->callback)
+        {
+            axis2_callback_report_error(args_list->callback, th_env,
+                AXIS2_ERROR_BLOCKING_INVOCATION_EXPECTS_RESPONSE);
+            axis2_callback_set_complete(args_list->callback, th_env, 
AXIS2_TRUE);
+        }
+    }
 
     axis2_op_ctx_free(op_ctx, th_env);
 
     th_pool = th_env->thread_pool;
 
+    axis2_callback_free(args_list->callback, th_env);
     AXIS2_FREE(th_env->allocator, args_list);
 
     if(th_env)
diff --git a/src/core/clientapi/svc_client.c b/src/core/clientapi/svc_client.c
index 7d41c3049..955cf5d4f 100644
--- a/src/core/clientapi/svc_client.c
+++ b/src/core/clientapi/svc_client.c
@@ -805,6 +805,7 @@ axis2_svc_client_send_receive_non_blocking_with_op_qname(
     const axiom_node_t * payload,
     axis2_callback_t * callback)
 {
+    axis2_callback_increment_ref(callback, env);
     axis2_msg_ctx_t *msg_ctx = NULL;
     AXIS2_TRANSPORT_ENUMS transport_in_protocol;
     axis2_bool_t qname_free_flag = AXIS2_FALSE;
@@ -870,7 +871,16 @@ axis2_svc_client_send_receive_non_blocking_with_op_qname(
         axis2_op_client_set_callback_recv(svc_client->op_client, env, 
svc_client->callback_recv);
     }
 
-    axis2_op_client_execute(svc_client->op_client, env, AXIS2_FALSE);
+    if(axis2_op_client_execute(svc_client->op_client, env, AXIS2_FALSE) != 
AXIS2_SUCCESS)
+    {
+        if(qname_free_flag)
+        {
+            axutil_qname_free((axutil_qname_t *)op_qname, env);
+        }
+        axis2_callback_free(callback, env);
+        return;
+    }
+
     axis2_svc_client_set_http_info(svc_client, env, msg_ctx);
     svc_client->auth_failed = axis2_msg_ctx_get_auth_failed(msg_ctx, env);
     svc_client->required_auth_is_http = 
axis2_msg_ctx_get_required_auth_is_http(msg_ctx, env);
@@ -884,6 +894,7 @@ axis2_svc_client_send_receive_non_blocking_with_op_qname(
         axutil_qname_free((axutil_qname_t *)op_qname, env);
         op_qname = NULL;
     }
+    axis2_callback_free(callback, env);
 }
 
 AXIS2_EXTERN void AXIS2_CALL

Reply via email to