mirror of
https://github.com/gnustep/libs-base.git
synced 2025-05-19 20:11:47 +00:00
Make NSAutoreleasePools co-exist with exceptions, safely and without
leaks. Also improve efficiency by using a linked list of arrays instead of realloc when more space is needed, and by using a cache of pools that helps avoid the allocation/deallocation of pools. (push_pool_to_cache): New function. (pop_pool_from_cache): New function. ([NSAutoreleasePool +initialize]): New method. ([NSAutoreleasePool +allocWithZone:]): New method. ([NSAutoreleasePool -init]): Method overhauled to initialize new ivar for handling exceptions, _child; also use pool cache. (total_autoreleased_objects_count): New static variable. (BEGINNING_POOL_SIZE): Macro renamed from DEFAULT_SIZE. (autorelease_pool_cache, autorelease_pool_cache_size, autorelease_pool_cache_count): New static variables. ([NSAutoreleasePool -_setChildPool:]): New method, handling stack and dealing with exceptions. ([NSAutoreleasePool -addObject:]): Deal with linked list of arrays. ([NSAutoreleasePool -dealloc]): Deal with exceptions by releasing child pools. Place self into the pool cache instead of deallocating. ([NSAutoreleasePool +resetTotalAutoreleasedObjects]): New method. ([NSAutoreleasePool +totalAutoreleasedObjects]): New method. git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@1311 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
parent
48f6007abf
commit
b5e1608e0c
1 changed files with 218 additions and 55 deletions
|
@ -24,52 +24,164 @@
|
|||
#include <objects/stdobjects.h>
|
||||
#include <Foundation/NSAutoreleasePool.h>
|
||||
#include <objects/objc-malloc.h>
|
||||
#include <objects/Array.h>
|
||||
#include <Foundation/NSException.h>
|
||||
#include <limits.h>
|
||||
|
||||
/* Doesn't handle multi-threaded stuff.
|
||||
Doesn't handle exceptions. */
|
||||
/* TODO:
|
||||
Doesn't work multi-threaded.
|
||||
|
||||
/* Put the stuff from initialize into a runtime init function.
|
||||
This class should be made more efficient, especially:
|
||||
[[NSAutorelease alloc] init]
|
||||
[current_pool addObject:o] (REALLOC-case)
|
||||
[[NSAutorelease alloc] init]
|
||||
[current_pool addObject:o] (REALLOC-case)
|
||||
*/
|
||||
|
||||
/* The current, default NSAutoreleasePool; the one that will hold
|
||||
objects that are arguments to [NSAutoreleasePool +addObject:]. */
|
||||
static NSAutoreleasePool *current_pool = nil;
|
||||
|
||||
/* When this is `NO', autoreleased objects are never actually sent a
|
||||
`release' message. Memory use grows, and grows, and... */
|
||||
/* When this is `NO', autoreleased objects are never actually recorded
|
||||
in an NSAutoreleasePool, and are not sent a `release' message.
|
||||
Thus memory for objects use grows, and grows, and... */
|
||||
static BOOL autorelease_enabled = YES;
|
||||
|
||||
/* When the released_count gets over this value, we raise an exception. */
|
||||
/* When the _released_count of the current pool gets over this value,
|
||||
we raise an exception. */
|
||||
static unsigned pool_count_warning_threshhold = UINT_MAX;
|
||||
|
||||
#define DEFAULT_SIZE 64
|
||||
/* The total number of objects autoreleased since the program was
|
||||
started, or since -resetTotalAutoreleasedObjects was called. */
|
||||
static unsigned total_autoreleased_objects_count = 0;
|
||||
|
||||
/* The size of the first _released array. */
|
||||
#define BEGINNING_POOL_SIZE 32
|
||||
|
||||
|
||||
@interface NSAutoreleasePool (Private)
|
||||
- _parentAutoreleasePool;
|
||||
- (unsigned) autoreleaseCount;
|
||||
- (unsigned) autoreleaseCountForObject: anObject;
|
||||
+ (unsigned) autoreleaseCountForObject: anObject;
|
||||
+ currentPool;
|
||||
- (void) _setChildPool: pool;
|
||||
@end
|
||||
|
||||
/* A cache of NSAutoreleasePool's already alloc'ed. Caching old pools
|
||||
instead of deallocating and re-allocating them will save time. */
|
||||
static id *autorelease_pool_cache;
|
||||
static int autorelease_pool_cache_size = 32;
|
||||
static int autorelease_pool_cache_count = 0;
|
||||
|
||||
static void
|
||||
push_pool_to_cache (id p)
|
||||
{
|
||||
if (autorelease_pool_cache_count == autorelease_pool_cache_size)
|
||||
{
|
||||
autorelease_pool_cache_size *= 2;
|
||||
OBJC_REALLOC (autorelease_pool_cache, id, autorelease_pool_cache_size);
|
||||
}
|
||||
autorelease_pool_cache[autorelease_pool_cache_count++] = p;
|
||||
}
|
||||
|
||||
static id
|
||||
pop_pool_from_cache ()
|
||||
{
|
||||
assert (autorelease_pool_cache_count);
|
||||
return autorelease_pool_cache[--autorelease_pool_cache_count];
|
||||
}
|
||||
|
||||
|
||||
@implementation NSAutoreleasePool
|
||||
|
||||
/* This method not in OpenStep */
|
||||
- parentAutoreleasePool
|
||||
+ (void) initialize
|
||||
{
|
||||
return parent;
|
||||
if (self == [NSAutoreleasePool class])
|
||||
OBJC_MALLOC (autorelease_pool_cache, id, autorelease_pool_cache_size);
|
||||
}
|
||||
|
||||
+ allocWithZone: (NSZone*)z
|
||||
{
|
||||
/* If there is an already-allocated NSAutoreleasePool available,
|
||||
save time by just returning that, rather than allocating a new one. */
|
||||
if (autorelease_pool_cache_count)
|
||||
return pop_pool_from_cache ();
|
||||
|
||||
return NSAllocateObject (self, 0, z);
|
||||
}
|
||||
|
||||
- init
|
||||
{
|
||||
if (!_released_head)
|
||||
{
|
||||
/* Allocate the array that will be the new head of the list of arrays. */
|
||||
_released = (struct autorelease_array_list*)
|
||||
(*objc_malloc) (sizeof(struct autorelease_array_list) +
|
||||
(BEGINNING_POOL_SIZE * sizeof(id)));
|
||||
/* Currently no NEXT array in the list, so NEXT == NULL. */
|
||||
_released->next = NULL;
|
||||
_released->size = BEGINNING_POOL_SIZE;
|
||||
_released->count = 0;
|
||||
_released_head = _released;
|
||||
}
|
||||
else
|
||||
/* Already initialized; (it came from autorelease_pool_cache);
|
||||
we don't have to allocate new array list memory. */
|
||||
{
|
||||
_released = _released_head;
|
||||
_released->count = 0;
|
||||
}
|
||||
|
||||
/* This NSAutoreleasePool contains no objects yet. */
|
||||
_released_count = 0;
|
||||
|
||||
/* Install ourselves as the current pool. */
|
||||
_parent = current_pool;
|
||||
_child = nil;
|
||||
[current_pool _setChildPool: self];
|
||||
current_pool = self;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) _setChildPool: pool
|
||||
{
|
||||
assert (!_child);
|
||||
_child = pool;
|
||||
}
|
||||
|
||||
/* This method not in OpenStep */
|
||||
- _parentAutoreleasePool
|
||||
{
|
||||
return _parent;
|
||||
}
|
||||
|
||||
/* This method not in OpenStep */
|
||||
- (unsigned) autoreleaseCount
|
||||
{
|
||||
return released_count;
|
||||
unsigned count = 0;
|
||||
struct autorelease_array_list *released = _released_head;
|
||||
while (released && released->count)
|
||||
{
|
||||
count += released->count;
|
||||
released = released->next;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* This method not in OpenStep */
|
||||
- (unsigned) autoreleaseCountForObject: anObject
|
||||
{
|
||||
unsigned count = 0;
|
||||
struct autorelease_array_list *released = _released_head;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < released_count; i++)
|
||||
if (released[i] == anObject)
|
||||
count++;
|
||||
while (released && released->count)
|
||||
{
|
||||
for (i = 0; i < released->count; i++)
|
||||
if (released->objects[i] == anObject)
|
||||
count++;
|
||||
released = released->next;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
@ -80,8 +192,8 @@ static unsigned pool_count_warning_threshhold = UINT_MAX;
|
|||
id pool = current_pool;
|
||||
while (pool)
|
||||
{
|
||||
count += [pool autoreleaseCountForObject:anObject];
|
||||
pool = [pool parentAutoreleasePool];
|
||||
count += [pool autoreleaseCountForObject: anObject];
|
||||
pool = [pool _parentAutoreleasePool];
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
@ -93,35 +205,56 @@ static unsigned pool_count_warning_threshhold = UINT_MAX;
|
|||
|
||||
+ (void) addObject: anObj
|
||||
{
|
||||
[current_pool addObject:anObj];
|
||||
[current_pool addObject: anObj];
|
||||
}
|
||||
|
||||
- (void) addObject: anObj
|
||||
{
|
||||
/* If the global, static variable AUTORELEASE_ENABLED is not set,
|
||||
do nothing, just return. */
|
||||
if (!autorelease_enabled)
|
||||
return;
|
||||
|
||||
if (released_count >= pool_count_warning_threshhold)
|
||||
if (_released_count >= pool_count_warning_threshhold)
|
||||
[NSException raise: NSGenericException
|
||||
format: @"AutoreleasePool count threshhold exceeded."];
|
||||
|
||||
if (released_count == released_size)
|
||||
/* Get a new array for the list, if the current one is full. */
|
||||
if (_released->count == _released->size)
|
||||
{
|
||||
released_size *= 2;
|
||||
OBJC_REALLOC(released, id, released_size);
|
||||
if (_released->next)
|
||||
{
|
||||
/* There is an already-allocated one in the chain; use it. */
|
||||
_released = _released->next;
|
||||
_released->count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We are at the end of the chain, and need to allocate a new one. */
|
||||
struct autorelease_array_list *new_released;
|
||||
unsigned new_size = _released->size * 2;
|
||||
|
||||
new_released = (struct autorelease_array_list*)
|
||||
(*objc_malloc) (sizeof(struct autorelease_array_list) +
|
||||
(new_size * sizeof(id)));
|
||||
new_released->next = NULL;
|
||||
new_released->size = new_size;
|
||||
new_released->count = 0;
|
||||
_released->next = new_released;
|
||||
_released = new_released;
|
||||
}
|
||||
}
|
||||
released[released_count] = anObj;
|
||||
released_count++;
|
||||
}
|
||||
|
||||
- init
|
||||
{
|
||||
parent = current_pool;
|
||||
current_pool = self;
|
||||
OBJC_MALLOC(released, id, DEFAULT_SIZE);
|
||||
released_size = DEFAULT_SIZE;
|
||||
released_count = 0;
|
||||
return self;
|
||||
/* Put the object at the end of the list. */
|
||||
_released->objects[_released->count] = anObj;
|
||||
(_released->count)++;
|
||||
|
||||
/* Keep track of the total number of objects autoreleased across all
|
||||
pools. */
|
||||
total_autoreleased_objects_count++;
|
||||
|
||||
/* Keep track of the total number of objects autoreleased in this pool */
|
||||
_released_count++;
|
||||
}
|
||||
|
||||
- (id) retain
|
||||
|
@ -138,28 +271,48 @@ static unsigned pool_count_warning_threshhold = UINT_MAX;
|
|||
|
||||
- (void) dealloc
|
||||
{
|
||||
int i;
|
||||
/* If there are NSAutoreleasePool below us in the stack of
|
||||
NSAutoreleasePools, then deallocate them also. The (only) way we
|
||||
could get in this situation (in correctly written programs, that
|
||||
don't release NSAutoreleasePools in weird ways), is if an
|
||||
exception threw us up the stack. */
|
||||
if (_child)
|
||||
[_child dealloc];
|
||||
|
||||
/* Make debugging easier by checking to see if we already dealloced the
|
||||
object before trying to release it. Also, take the object out of the
|
||||
released list just before releasing it, so if we are doing
|
||||
"double_release_check"ing, then autoreleaseCountForObject: won't find the
|
||||
object we are currently releasing. */
|
||||
for (i = 0; i < released_count; i++)
|
||||
{
|
||||
id anObject = released[i];
|
||||
if (object_get_class(anObject) == (void*) 0xdeadface)
|
||||
[NSException
|
||||
raise: NSGenericException
|
||||
format: @"Autoreleasing deallocated object.\n"
|
||||
@"Suggest you debug after setting [NSObject "
|
||||
@"enableDoubleReleaseCheck:YES]\nto check for release errors."];
|
||||
released[i]=0;
|
||||
[anObject release];
|
||||
}
|
||||
OBJC_FREE(released);
|
||||
current_pool = parent;
|
||||
NSDeallocateObject(self);
|
||||
/* Make debugging easier by checking to see if the user already
|
||||
dealloced the object before trying to release it. Also, take the
|
||||
object out of the released list just before releasing it, so if
|
||||
we are doing "double_release_check"ing, then
|
||||
autoreleaseCountForObject: won't find the object we are currently
|
||||
releasing. */
|
||||
{
|
||||
struct autorelease_array_list *released = _released_head;
|
||||
int i;
|
||||
|
||||
while (released)
|
||||
{
|
||||
for (i = 0; i < released->count; i++)
|
||||
{
|
||||
id anObject = released->objects[i];
|
||||
if (object_get_class(anObject) == (void*) 0xdeadface)
|
||||
[NSException
|
||||
raise: NSGenericException
|
||||
format: @"Autoreleasing deallocated object.\n"
|
||||
@"Suggest you debug after setting [NSObject "
|
||||
@"enableDoubleReleaseCheck:YES]\n"
|
||||
@"to check for release errors."];
|
||||
released->objects[i] = nil;
|
||||
[anObject release];
|
||||
}
|
||||
released = released->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Uninstall ourselves as the current pool; install our parent parent. */
|
||||
current_pool = _parent;
|
||||
|
||||
/* Don't deallocate ourself, just save us for later use. */
|
||||
push_pool_to_cache (self);
|
||||
}
|
||||
|
||||
- autorelease
|
||||
|
@ -169,6 +322,16 @@ static unsigned pool_count_warning_threshhold = UINT_MAX;
|
|||
return self;
|
||||
}
|
||||
|
||||
+ (void) resetTotalAutoreleasedObjects
|
||||
{
|
||||
total_autoreleased_objects_count = 0;
|
||||
}
|
||||
|
||||
+ (unsigned) totalAutoreleasedObjects
|
||||
{
|
||||
return total_autoreleased_objects_count;
|
||||
}
|
||||
|
||||
+ (void) enableRelease: (BOOL)enable
|
||||
{
|
||||
autorelease_enabled = enable;
|
||||
|
|
Loading…
Reference in a new issue