IntroductionOne of the main aspects of Axis2/C (or in any program written in C) is managing the memory. The concept of memory management applies to core Axis2 development, plugin/module writers as well as to the users who write services and clients. Implications of improper memory management can mainly be two fold. If the allocated memory is not disposed properly at the end of its lifetime, the heap memory grows indefinitely and ultimately will cause an "out of memory error". On the other hand, if freeing memory is done carelessly (i.e. without considering the liveness of the memory object) Axis2 will end up with nasty segmentation faults.
Axis2/C Pluggable Memory AllocatorOne of the key design features of Axis2/C is the ability to plug it into different environments. As an example, a plugin to the PHP engine using Axis2/C will add Web service capabilities to the php language. In this kind of situation it is very important to share the resources such as memory allocators, memory pools, thread pools etc. which are available through the environment. So the Axis2/C design catered to this important requirement by adding an environment object which can be passed around every function call inside Axis2 core. This environment consists of several entities which are used to abstract the environment it is running. One of the heavily used entities here is the allocator. Allocator is the wrapper for memory management mechanisms. It defines the following three primitives.
- malloc_fn - method to allocate a memory block of given size
- realloc - method to change the size of the memory block
- free_fn - method to free a memory block
At the point of integration with different environments one can create a new allocator which uses the particular environment specific functions to manage memory. A default allocator is also provided which uses malloc, free and realloc functions provided by c runtime library.
Memory ScopesIn Axis2/C managing memory is not an easy task since the execution model that Axis2 follows is not simply request-reply. If it is a simple request-reply execution model, the design is straight forward - free everything that are related to that message exchange after sending the reply. But when the MEP(Message Exchange Pattern) is complex, deciding when to free memory becomes complicated because of the involvement of the context hierarchy. For example, in an in-out MEP, a property which is put into the operation context shouldn't be freed at the end of in-pipe, as it might be needed when constructing the reply message. At the same time, in an in-only MEP, everything should be free'd at the end of the in-pipe in order to avoid a memory leak. So in order to inform the user about this scope concept Axis2/C has defined three basic memory block scopes. Users are also free to define their own scopes as well. They are :
- AXIS2_SCOPE_APPLICATION - Represent memory objects those should stay during the entire lifetime of Axis2 engine
- AXIS2_SCOPE_SESSION - Represent memory objects those should stay during an application session, which can consist several request-reply exchanges.
- AXIS2_SCOPE_REQUEST - Represent memory objects those should be freed at the end of request/reply.
When to Free Memory (and When Not to)?Understanding the scope or the "liveness" of the allocated object is the key point behind successful memory management. This can be very difficult in asynchronous operations, since the lifetime of the in-pipe as well as the in message context ends without sending the reply to the destination, hence making it difficult to make a decision on what to free and what not to free. At the same time the module authors should be cautious when putting properties to the context hieracy and make sure they put the object in the correct place of the hierarchy. As an example consider a module which has an in handler as well as an out handler which are targeted to operate on both request and the reply of an in-out MEP. If the in handler wants to share something with the out-handler which is specific to a communication, it should be put in the operation context. If it is put in the message context it will be freed at the end of in-pipe hence shortening the lifetime of the object than expected. If it is put to the service context it will stay during the service lifetime hence lengthening the lifetime of the object than expected. So here are some basic rules which will help in the process of deciding "when to free memory":
- Free the memory of the temporary variables as soon as they are used.
- For properties and the pseudo objects set into context hierarchy, look where it is used again and derive the lifetime of that. At the point where the object ends its life time, free it. This step involves deciding the proper place of context hierarchy as well.
- Try to use the builtin Memory Management mechanism provided by Axis2 core, when properties are set to context hierarchy.
Using Inbuilt Memory Management Mechanism in Axis2/CAll the properties put into the context hierarchy should be wrapped inside an axis2_property_t object. This wrapper provides two main facilities. First, the user can define the lifetime of the property by setting the scope of the property object (eg: AXIS2_SCOPE_REQUEST denotes request only properties and hence will be free'd along with the message context). This helps users to put allocated data to the contexts and let the Axis2 core to manage the memory.
Second, users can specify their own free functions to the objects they are setting as properties. This helps Axis2 core to free the memory correctly (eg: a pseudo object represented by a struct cannot be simply free'd calling the free(struct) because it may contain other allocated objects inside that. So the proper psudo_object_name_free() has to be implemented and that is the one which should be called by Axis2 core while freeing the property. In the property object there's a function pointer named free_fn which can be assigned to this object specific free function and that will be called by Axis2 core while freeing the property. The prototype of this function pointer is:
typedef int (AXIS2_CALL *AXIS2_FREE_VOID_ARG)
(void *obj_to_be_freed, const axis2_env_t *env);
The pseudo object which has to be free'd is passed as a void argument in the first parameter. Second is the Axis2 environment. Following code segment illustrates how to set a hash map as a property.
/* Create a property object using the axis2 environment */
axis2_property_t *property = axis2_property_create(env);
/* Create the property which is needed to put to hierarchy
* Here it is a hash map
axis2_hash_t *key_map = axis2_hash_make(env);
/* Denote that the property should be freed at the end of
* the request/reply
AXIS2_PROPERTY_SET_SCOPE(property, env, AXIS2_SCOPE_REQUEST);
/* Set the pseudo object specific free function to the property
* the axis2_hash_free_void_arg is in the format:
* int axis2_hash_free_void_arg(void *hash_map, const axis2_env_t *env)
AXIS2_PROPERTY_SET_FREE_FUNC(property, env, axis2_hash_free_void_arg);
/* Now wrap the hash map with the property */
AXIS2_PROPERTY_SET_VALUE(property, env, key_map);
/* Set the property to hierarchy */
AXIS2_MSG_CTX_SET_PROPERTY(msg_ctx, env, "KEY_MAP", property, AXIS2_FALSE);