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