|
From: D. L. <lo...@no...> - 2003-04-16 12:45:01
|
Hi,
I did some work on the shutdown/memory leak issue. My problem was, that
after using log4cpp and the PropertyConfigurator I have lots of small
memory leaks during application shutdown. Calling Category::shutdown
will not help in this issue. I did some investigation and came up with
the following solution:
1.) The PropertyConfigurator allocates all declared appenders in the
configuration file, but will not transfer ownership to one of the
categories. This results in the appenders not beeing deleted during
shutdown, since the Categories have not set the _ownsAppender flag to true.
There are two solutions to the problem:
a.) transfer the ownership of the appender to at least one Category or
introduce a dummy Category with the ownership of all instanciated appenders.
b.) call Appender::_deleteAllAppenders somewhere during shutdown.
This yields the question to whom the AppenderMap belongs, and who is
responsible for deleting the appenders. one solution would be to do
the deletion via a static variable within the Appender class, but this
results in a circular dependancy with the HierarchyMaintainer. The
HierarchyMaintainer exist earlier than the AppenderMap, and therefor
will be deleted later. Within the destructor of the HierarchyMaintainer
the deleteAllCategories function is called where all Categories and the
contained appenders will be deleted. Deleting the AppenderMap before
this call will result in a crash during destruction. One could force the
creation of the AppenderMap before the creation of the
HierarchyMaintainer, but this is also not good programming practice.
I solved the problem right now via a explicit call to deleteAllAppenders
during the shutdown of the HierarchyMaintainer.
2.) deletion of the AppenderMap itself
Don't know how to that correct, yet. I added a delete call for
Appender::_allAppenders into the Appender::_deleteAllAppenders ()
function, since the map isn't used after a call to this function.
This should be better done via a specifc AppenderMap class, that can
handle its contents and the map itself. And again, we must force this
AppenderMap to exist before the HierarchyMaintainer, since we otherwise
get shutdown oder dependancy.
3.) deletion of the NDC
The ThreadLocalDataHolder should reset its content during destruction to
delete the stored data object
4.) delete the HierarchyMaintainer.
This is a curcial part, since the HierarchyMaintainer should delete all
Categories during destruction. But I use a lot of static references to
Categories within my application, that will be invalidated in a wrong
shutdown sequence. The problem is, that there is no guarantee in wich
order static variables are constructed. So simply adding a shutdown
class to the HierarchyMainainter will not solve the problem, since an
other static Category& variable could have been initialized before the
shutdown member was initialized.
First I thought to introduce the a LogManager (like in Log4j) but this
doesn't solve the problem, only shifts it away from the
HierarchyMaintainer. The I realized, that I must add an implicit order
into the atexit sequence to install an atexit handler in the moment the
first static Category& is iniitialized. This can be done with an
auto_ptr in the HierarchyMaintainer like so:
HierarchyMaintainer* HierarchyMaintainer::_defaultMaintainer = NULL;
HierarchyMaintainer& HierarchyMaintainer::getDefaultMaintainer() {
if (!_defaultMaintainer) {
static std::auto_ptr<HierarchyMaintainer> maintainer (new
HierarchyMaintainer());
_defaultMaintainer = maintainer.get ();
}
return *_defaultMaintainer;
}
By stuffing the allocated HierarchyMaintainer into a static auto_ptr
within the getDefaultMaintainer function we will hook into the atexit
queue in the moment, the first time this function is called, and that is
exactly one element before the first static Category& that depends on
the HierarchMaintainer.
With this the HierarchyMaintainer will be deleted during application
shutdown, and all resources are freed.
On little problem still exists: What if after the destruction of the
HierarchyMaintainer someone tries to get a reference to a Category. This
again will fire up the HierarchyMaintainer resulting in the allocation
of objects.
The solution to this is not to use local references to Categories, only
static references.
A second solution would be to use the LogManager/LoggerRepository class
from log4j, that introduces a variable repository for Logger objects.
The default repository would be the HierarchyMaintainer. All calls to
getCategory/getLogger are redirected through the LogManager. After
shutdown one could return a static "do nothing" category from the
LogManager. Then, if you call the LogManager::getLogger("whatever")
after shutdown of the log4cpp library you get a valid logger, but this
logger will not do anything and all references to static Categories are
still valid until the HierarchyMaintainer is deleted.
-----
As a second solution I would like to say, that the Log4cppCleanup class
wasn't that bad at all. I think now, that it was was simply wrong
allocated. If you would do it with the auto_ptr method as described
above and make a call to the Log4cppCleanup::getInstance function from
all allocations of static variables it would have worked.
static Log4cppCleanup::instance* = NULL;
Log4cppCleanup::getInstance ()
{
if (!instance)
{
// the auto_ptr will force the deletion of the Log4cppCleanup
// after all depending objects have been deleted
static std::auto_ptr <Log4cppCleanup> autoInstace (new Log4cppCleanup);
instance = autoInstace.get ();
}
return instance;
}
HierarchyMaintainer& HierarchyMaintainer::getDefaultMaintainer()
{
if (!_defaultMaintainer)
{
// force the Cleanup class into the atExit queue
Log4cppCleanup::getInstance ();
// don't need the auto_ptr here, since all cleanup is done
// via Log4ccpCleanup
_defaultMaintainer = new HierarchyMaintainer();
}
return *_defaultMaintainer;
}
----------
I attach a patch to the CVS checkout from 14.04.2003 to implement all
that stuff, except the proper handling of local categories during
shutdown returned from LogManager::getLogger ().
The patch includes also a change for the Appender::getAllAppenders to
return a vector<Appender*>* and an additional method for the
FileAppender to return its name. An last but not least I included a file
named dontdiff to use for building diffs, to exclude unwanted files in
the diff:
> diff -Naur -X log4cpp/dontdiff ...
Perhaps you can give me some feedback about the two proposed solutions.
Thanks
Dirk
|