You are viewing a plain text version of this content. The canonical link for it is here.
Posted to c-commits@axis.apache.org by ro...@apache.org on 2026/09/02 18:38:32 UTC

(axis-axis2-c-core) 02/02: Keep transport libraries mapped for as long as their pointers live

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

commit 4b2aa7e5cee8cf714724871c2afdf116409adb13
Author: Robert Lazarski <ro...@gmail.com>
AuthorDate: Wed Sep 2 07:36:50 2026 -1000

    Keep transport libraries mapped for as long as their pointers live
    
    A transport hands out function pointers that outlive it. The HTTP
    sender puts the http client into a message context property whose free
    function lives in libaxis2_http_sender, and on the client that property
    sits in the map shared with axis2_options, released by
    axis2_options_free -- which svc_client_free runs after
    axis2_conf_ctx_free has unloaded that library. Every sample client
    jumped into unmapped memory at exit. The transports' descriptors now
    detach the library instead of closing it, which cost two things: the
    handle setter rejected the NULL that detaching needs, and freed the old
    handle with AXIS2_FREE though dlopen never allocated it.
    
    Co-Authored-By: Claude Opus 5 (1M context) <no...@anthropic.com>
---
 src/core/deployment/conf_builder.c | 40 ++++++++++++++++++++++++++++++++++++--
 util/src/dll_desc.c                | 18 +++++++++++------
 2 files changed, 50 insertions(+), 8 deletions(-)

diff --git a/src/core/deployment/conf_builder.c b/src/core/deployment/conf_builder.c
index f675dc423..46544951c 100644
--- a/src/core/deployment/conf_builder.c
+++ b/src/core/deployment/conf_builder.c
@@ -30,6 +30,37 @@ struct axis2_conf_builder
     struct axis2_desc_builder *desc_builder;
 };
 
+/**
+ * Free a transport's dll descriptor without unloading the library.
+ *
+ * A transport leaves function pointers behind it. The HTTP sender puts the
+ * http client into a message context property whose free function,
+ * axis2_http_client_free_void_arg, lives in libaxis2_http_sender; on the
+ * client that property goes into the map shared with axis2_options and is
+ * released by axis2_options_free. svc_client_free runs that *after*
+ * axis2_conf_ctx_free -- deliberately, see the AXIS2C-1635 note there -- so by
+ * then the library holding the free function has been unloaded and the call
+ * jumps into unmapped memory.
+ *
+ * Clearing the handle first means the descriptor is freed and the code stays
+ * mapped. That costs one address-space mapping per transport for the life of
+ * the process, of which there are one or two, and it removes a whole class of
+ * crash: any pointer into a transport that outlives the conf. Services and
+ * modules are untouched -- they are genuinely reloadable and are unloaded
+ * through their own paths.
+ */
+static void AXIS2_CALL
+axis2_conf_builder_transport_dll_desc_free(
+    void *dll_desc,
+    const axutil_env_t *env)
+{
+    if(dll_desc)
+    {
+        axutil_dll_desc_set_dl_handler((axutil_dll_desc_t *)dll_desc, env, NULL);
+        axutil_dll_desc_free((axutil_dll_desc_t *)dll_desc, env);
+    }
+}
+
 static axis2_status_t
 axis2_conf_builder_process_disp_order(
     axis2_conf_builder_t * conf_builder,
@@ -815,7 +846,10 @@ axis2_conf_builder_process_transport_senders(
 
                 axutil_dll_desc_set_type(dll_desc, env, AXIS2_TRANSPORT_SENDER_DLL);
                 axutil_param_set_value(impl_info_param, env, dll_desc);
-                axutil_param_set_value_free(impl_info_param, env, axutil_dll_desc_free_void_arg);
+                /* Not axutil_dll_desc_free_void_arg: see the note on this
+                 * function -- the sender's code must outlive the conf. */
+                axutil_param_set_value_free(impl_info_param, env,
+                    axis2_conf_builder_transport_dll_desc_free);
                 axutil_class_loader_init(env);
                 transport_sender = axutil_class_loader_create_dll(env, impl_info_param);
                 axis2_transport_out_desc_add_param(transport_out, env, impl_info_param);
@@ -1123,7 +1157,9 @@ axis2_conf_builder_process_transport_recvs(
                 axutil_dll_desc_set_type(dll_desc, env, AXIS2_TRANSPORT_RECV_DLL);
 
                 axutil_param_set_value(impl_info_param, env, dll_desc);
-                axutil_param_set_value_free(impl_info_param, env, axutil_dll_desc_free_void_arg);
+                /* Same as the sender above. */
+                axutil_param_set_value_free(impl_info_param, env,
+                    axis2_conf_builder_transport_dll_desc_free);
                 axutil_class_loader_init(env);
                 recv = (axis2_transport_receiver_t *)axutil_class_loader_create_dll(env,
                     impl_info_param);
diff --git a/util/src/dll_desc.c b/util/src/dll_desc.c
index 4b65945d5..61808dcd0 100644
--- a/util/src/dll_desc.c
+++ b/util/src/dll_desc.c
@@ -184,12 +184,18 @@ axutil_dll_desc_set_dl_handler(
     AXIS2_DLHANDLER dl_handler)
 {
     AXIS2_ENV_CHECK(env, AXIS2_FAILURE);
-    AXIS2_PARAM_CHECK(env->error, dl_handler, AXIS2_FAILURE);
-
-    if(dll_desc->dl_handler)
-    {
-        AXIS2_FREE(env->allocator, dll_desc->dl_handler);
-    }
+    AXIS2_PARAM_CHECK(env->error, dll_desc, AXIS2_FAILURE);
+
+    /* NULL is allowed, and is the point: it detaches the library from the
+     * descriptor so freeing the descriptor does not unload it. A caller that
+     * has handed function pointers out of a module needs that -- the pointers
+     * outlive the descriptor.
+     *
+     * The previous rejection of NULL made clearing impossible, and the branch
+     * it guarded freed the old handle with AXIS2_FREE. That handle comes from
+     * dlopen, not from the allocator, so it was never the allocator's to
+     * release; it never fired only because the sole caller sets a handle once,
+     * onto a descriptor created with none. */
     dll_desc->dl_handler = dl_handler;
     return AXIS2_SUCCESS;
 }