Start stuff for handling cleanup on process exit.

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@33344 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Richard Frith-MacDonald 2011-06-19 09:26:03 +00:00
parent db2099074d
commit 9831216043
12 changed files with 246 additions and 9 deletions

View file

@ -1,3 +1,28 @@
2011-06-19 Richard Frith-Macdonald <rfm@gnu.org>
* Headers/GNUstepBase/NSObject+GNUstepBase.h:
* Source/Additions/NSObject+GNUstepBase.m:
Add new category with methods for cleanup of long-lived data structures
on process exit.
* Source/NSObject.m: Check GNUSTEP_SHOULD_CLEAN_UP environment variable
and call +shouldCleanUp: if it is set.
* Documentation/Base.gsdoc: Document new environment variable.
* Source/GSLocale.m:
* Source/NSDistributedNotificationCenter.m:
* Source/NSUserDefaults.m:
* Source/NSNotificationCenter.m:
* Source/GSPrivate.h:
* Source/externs.m:
* Source/NSString.m:
Add some cleanup support.
Classes may perform cleanup in a +atExit method which is automatically
called on process exit.
Classes may use [NSObject+leak:] to 'leak' an object so that it is
retained for the life of the process, but released immediately before
the +atExit methods are called if GNUSTEP_SHOULD_CLEAN_UP is YES.
See the NSObject(AtExit) category documentation for details.
2011-06-18 Sebastian Reitenbach <sebastia@l00-bugdead-prods.de>
* Source/NSObject.m: fix for clang

View file

@ -269,6 +269,22 @@ notice and this notice are preserved.
core dump on systems where that is possible.
</p>
</desc>
<term>GNUSTEP_SHOULD_CLEAN_UP</term>
<desc>
<p>
When this is set to YES, the GNUstep extension method
+setShouldCleanUp: is called when the NSObject class is
initialised, this turns on recording of some intentionally
leaked memory (data structures intended to persist for the
whole life of the process), and activates cleanup of that
memory on process exit so that external tools such as
valgrind will not report the memory as possibly lost.
</p>
<p>
Use of this facility is a work in progress ... many classes
do not yet clean up after themselves when this is enabled.
</p>
</desc>
<term>GNUSTEP_STACK_TRACE</term>
<desc>
<p>

View file

@ -96,6 +96,67 @@ extern "C" {
@end
/** Category for methods handling leaked memory cleanup on exit of process
* (for use when debugging memory leaks).<br />
* You enable this by calling the +setShouldCleanUp: method (done implicitly
* by gnustep-base if the GNUSTEP_SHOULD_CLEAN_UP environment variable is
* set to YES).<br />
* Your class then has two options for performing cleanup when the process
* ends:
* <p>1. Use the +leak: method to register objects which are simply to be
* retained until the process ends, and then either ignored or released
* depending on the cleanup setting in force. This mechanism is simple
* and should be sufficient for many classes.
* </p>
* <p>2. Implement a +atExit method to be run when the process ends and,
* within that method call +shouldCleanUp to determine whether cleanup
* should be done, and if it returns YES then perform any complex cleanup
* tasks for the class.
* </p>
*/
@interface NSObject(atExit)
/** This method is called on exit for any class which implements it.<br />
* The order in which methods for different classes is called is indeterminate
* so the method must not depend on any other class being in a usable state
* at the point when the method is called (rather like +load).<br />
* Typical use would be to release memory occupied by class data structures
* so that memory usage analysis software will not think the memory has
* been leaked.
*/
+ (void) atExit;
/** Activates support for the +atExit method.
*/
+ (void) enableAtExit;
/** This method simply retains its argument so that it will never be
* deallocated during normal operation, but keeps track of it so that
* it is released during process exit (at the point immediately before
* calls to +atExit methods are performed) if cleanup is enabled.<br />
* Returns its argument.
*/
+ (id) leak: (id)anObject;
/** Specifies the default cleanup behavior on process exit ... the value
* returned by the NSObject implementation of the +shouldClanUp method.<br />
* Calling this method with a YES argument implicitly calls the +enableAtExit
* method as well.<br />
* The GNUstep Base library calls this method with the value obtained from
* the GNUSTEP_SHOULD_CLEAN_UP environment variable when NSObject is
* initialised.
*/
+ (void) setShouldCleanUp: (BOOL)aFlag;
/** Returns a flag indicating whether the receiver should clean up
* its data structures etc at process exit.<br />
* The NSObject implementation returns the value set by the +setShouldCleanUp:
* method but subclasses may override this.
*/
+ (BOOL) shouldCleanUp;
@end
#endif /* OS_API_VERSION */
#if defined(__cplusplus)

View file

@ -23,7 +23,9 @@
*/
#import "common.h"
#import "Foundation/NSArray.h"
#import "Foundation/NSException.h"
#import "Foundation/NSLock.h"
#import "GNUstepBase/NSObject+GNUstepBase.h"
#import "GNUstepBase/NSDebug+GNUstepBase.h"
@ -125,3 +127,92 @@
@end
static NSMutableArray *leaked = nil;
static NSLock *leakLock = nil;
static BOOL shouldCleanUp = NO;
static void
handleExit()
{
int classCount;
if (YES == shouldCleanUp)
{
DESTROY(leaked);
DESTROY(leakLock);
}
classCount = objc_getClassList(NULL, 0);
if (classCount > 0)
{
Class *classes;
int index;
classes = malloc(sizeof(Class) * classCount);
classCount = objc_getClassList(classes, classCount);
for (index = 0; index < classCount; index++)
{
Class c = classes[index];
Method m = class_getClassMethod(c, @selector(atExit));
if (m != 0)
{
Class s = class_getSuperclass(c);
if (0 == s || class_getClassMethod(s, @selector(atExit)) != m)
{
[c atExit];
}
}
}
free(classes);
}
}
@implementation NSObject(atExit)
+ (void) enableAtExit
{
if (nil == leakLock)
{
leakLock = [NSLock new];
atexit(handleExit);
}
}
+ (void) atExit
{
return;
}
+ (id) leak: (id)anObject
{
[leakLock lock];
if (nil == leaked)
{
leaked = [NSMutableArray new];
}
[leaked addObject: anObject];
[leakLock unlock];
return anObject;
}
+ (void) setShouldCleanUp: (BOOL)aFlag
{
if (YES == aFlag)
{
[self enableAtExit];
shouldCleanUp = YES;
}
else
{
shouldCleanUp = NO;
}
}
+ (BOOL) shouldCleanUp
{
return shouldCleanUp;
}
@end

View file

@ -24,6 +24,7 @@
*/
#import "common.h"
#import "GNUstepBase/GSLocale.h"
#import "GNUstepBase/NSObject+GNUstepBase.h"
#import "Foundation/NSDictionary.h"
#import "Foundation/NSArray.h"
#import "Foundation/NSLock.h"
@ -218,7 +219,7 @@ GSDomainFromDefaultLocale(void)
*/
if (saved == nil)
{
saved = [dict mutableCopy];
saved = [NSObject leak: dict];
}
[gnustep_global_lock unlock];
return saved;

View file

@ -161,8 +161,8 @@ __attribute__((unused)) static void GSFreeTempBuffer(void **b)
* Yet the optimization of the stored hash value is currently deemed
* more important.
*/
#define GS_REPLACE_CONSTANT_STRING(ID) \
ID = [[NSStringClass alloc] initWithCString: [ID cString]]
#define GS_REPLACE_CONSTANT_STRING(ID) [(ID = [NSObject \
leak: [[NSString alloc] initWithCString: [ID cString]]]) release]
/* Using cString here is OK here
because NXConstantString returns a pointer
to it's internal pointer. */

View file

@ -38,6 +38,7 @@
#import "Foundation/NSRunLoop.h"
#import "Foundation/NSTask.h"
#import "GNUstepBase/NSTask+GNUstepBase.h"
#import "GNUstepBase/NSObject+GNUstepBase.h"
#import "Foundation/NSDistributedNotificationCenter.h"
#import "Foundation/NSUserDefaults.h"
#import "Foundation/NSHost.h"
@ -151,7 +152,8 @@ static NSDistributedNotificationCenter *netCenter = nil;
NSAllocateObject(self, 0, NSDefaultMallocZone());
tmp->_centerLock = [NSRecursiveLock new];
tmp->_type = RETAIN(NSLocalNotificationCenterType);
locCenter = tmp;
locCenter = [NSObject leak: tmp];
[tmp release];
}
NS_HANDLER
{
@ -179,7 +181,8 @@ static NSDistributedNotificationCenter *netCenter = nil;
NSAllocateObject(self, 0, NSDefaultMallocZone());
tmp->_centerLock = [NSRecursiveLock new];
tmp->_type = RETAIN(GSPublicNotificationCenterType);
pubCenter = tmp;
pubCenter = [NSObject leak: tmp];
[tmp release];
}
NS_HANDLER
{
@ -207,7 +210,8 @@ static NSDistributedNotificationCenter *netCenter = nil;
NSAllocateObject(self, 0, NSDefaultMallocZone());
tmp->_centerLock = [NSRecursiveLock new];
tmp->_type = RETAIN(GSNetworkNotificationCenterType);
netCenter = tmp;
netCenter = [NSObject leak: tmp];
[tmp release];
}
NS_HANDLER
{

View file

@ -417,6 +417,8 @@ static void endNCTable(NCTable *t)
GSIMapNode n0;
Observation *l;
TEST_RELEASE(t->_lock);
/*
* Free observations without notification names or numbers.
*/
@ -472,8 +474,6 @@ static void endNCTable(NCTable *t)
}
NSZoneFree(NSDefaultMallocZone(), t->chunks);
NSZoneFree(NSDefaultMallocZone(), t);
TEST_RELEASE(t->_lock);
}
static NCTable *newNCTable(void)
@ -684,6 +684,14 @@ purgeCollectedFromMapNode(GSIMapTable map, GSIMapNode node)
static NSNotificationCenter *default_center = nil;
+ (void) atExit
{
id tmp = default_center;
default_center = nil;
[tmp release];
}
+ (void) initialize
{
if (self == [NSNotificationCenter class])

View file

@ -47,6 +47,7 @@
#import "Foundation/NSNotification.h"
#import "Foundation/NSMapTable.h"
#import "GNUstepBase/GSLocale.h"
#import "GNUstepBase/NSObject+GNUstepBase.h"
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
@ -71,7 +72,6 @@
#define IN_NSOBJECT_M 1
#import "GSPrivate.h"
/* When this is `YES', every call to release/autorelease, checks to
make sure isn't being set up to release itself too many times.
This does not need mutex protection. */
@ -1112,6 +1112,14 @@ objc_create_block_classes_as_subclasses_of(Class super);
* string constants etc.
*/
NSConstantStringClass = [NSString constantStringClass];
/* See if we should cleanup at process exit.
*/
if (YES == GSPrivateEnvironmentFlag("GNUSTEP_SHOULD_CLEAN_UP", NO))
{
[self setShouldCleanUp: YES];
}
GSPrivateBuildStrings();
/* Determine zombie management flags and set up a map to store

View file

@ -558,6 +558,19 @@ handle_printf_atsign (FILE *stream,
}
#endif /* HAVE_REGISTER_PRINTF_FUNCTION */
+ (void) atExit
{
NSMapTable *t = placeholderMap;
if (0 != t)
{
DESTROY(defaultPlaceholderString);
DESTROY(placeholderLock);
placeholderMap = 0;
NSFreeMapTable(t);
}
}
+ (void) initialize
{
/*

View file

@ -363,6 +363,15 @@ newLanguages(NSArray *oldNames)
*/
@implementation NSUserDefaults: NSObject
+ (void) atExit
{
id tmp;
tmp = sharedDefaults;
sharedDefaults = nil;
[tmp release];
}
+ (void) initialize
{
if (self == [NSUserDefaults class])

View file

@ -26,6 +26,7 @@
#import "Foundation/NSArray.h"
#import "Foundation/NSException.h"
#import "GNUstepBase/NSObject+GNUstepBase.h"
#import "GSPrivate.h"