Fix thread memory leak.

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@17336 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
CaS 2003-07-25 09:27:44 +00:00
parent e8fb1797f9
commit 778c3a3c7c
3 changed files with 152 additions and 122 deletions

View file

@ -4,6 +4,10 @@
change ... seemed to cause problems on some systems. change ... seemed to cause problems on some systems.
* Tools/gdnc.m: ditto. * Tools/gdnc.m: ditto.
* Source/NSDebug.m: Small thread safely fix. * Source/NSDebug.m: Small thread safely fix.
* Source/NSThread.m: Avoid multiple copies of housekeeping timer.
* Source/NSConnection.m: Fix thread related memory leak leaving
an NSRunLoop in a connection when the connection is no longer
using it.
2003-07-23 Richard Frith-Macdonald <rfm@gnu.org> 2003-07-23 Richard Frith-Macdonald <rfm@gnu.org>

View file

@ -2633,122 +2633,160 @@ static void callEncoder (DOContext *ctxt)
NSTimeInterval delay_interval = last_interval; NSTimeInterval delay_interval = last_interval;
NSDate *delay_date = nil; NSDate *delay_date = nil;
NSRunLoop *runLoop = [runLoopClass currentRunLoop]; NSRunLoop *runLoop = [runLoopClass currentRunLoop];
BOOL isLocked = NO;
BOOL addedRunLoop = NO;
if (debug_connection > 5) /*
NSLog(@"Waiting for reply sequence %d on %x:%x", * If we have sent out a request on a run loop that we don't already
sn, self, [NSThread currentThread]); * know about, it must be on a new thread - so if we have multipleThreads
M_LOCK(_queueGate); * enabled, we must add the run loop of the new thread so that we can
while (_isValid == YES * get the reply in this thread.
&& (node = GSIMapNodeForKey(_replyMap, (GSIMapKey)sn)) != 0 */
&& node->value.obj == dummyObject) if (_multipleThreads == YES
&& [_runLoops indexOfObjectIdenticalTo: runLoop] == NSNotFound)
{ {
M_UNLOCK(_queueGate); [self addRunLoop: runLoop];
if (timeout_date == nil) addedRunLoop = YES;
{ }
timeout_date = [dateClass allocWithZone: NSDefaultMallocZone()];
timeout_date
= [timeout_date initWithTimeIntervalSinceNow: _replyTimeout];
}
if (_multipleThreads == YES)
{
NSDate *limit_date;
NSTimeInterval next_interval;
/* NS_DURING
* If multiple threads are using this connections, another {
* thread may read the reply we are waiting for - so we must if (debug_connection > 5)
* break out of the runloop frequently to check. We do this NSLog(@"Waiting for reply sequence %d on %x:%x",
* by setting a small delay and increasing it each time round sn, self, [NSThread currentThread]);
* so that this semi-busy wait doesn't consume too much M_LOCK(_queueGate); isLocked = YES;
* processor time (I hope). while (_isValid == YES
* We set an upper limit on the delay to avoid responsiveness && (node = GSIMapNodeForKey(_replyMap, (GSIMapKey)sn)) != 0
* problems. && node->value.obj == dummyObject)
*/ {
RELEASE(delay_date); M_UNLOCK(_queueGate); isLocked = NO;
delay_date = [dateClass allocWithZone: NSDefaultMallocZone()]; if (timeout_date == nil)
if (delay_interval < 1.0)
{ {
next_interval = last_interval + delay_interval; timeout_date = [dateClass allocWithZone: NSDefaultMallocZone()];
last_interval = delay_interval; timeout_date
delay_interval = next_interval; = [timeout_date initWithTimeIntervalSinceNow: _replyTimeout];
} }
delay_date if (_multipleThreads == YES)
= [delay_date initWithTimeIntervalSinceNow: delay_interval];
/*
* We must not set a delay date that is further in the future
* than the timeout date for the response to be returned.
*/
if ([timeout_date earlierDate: delay_date] == timeout_date)
{ {
limit_date = timeout_date; NSDate *limit_date;
NSTimeInterval next_interval;
/*
* If multiple threads are using this connections, another
* thread may read the reply we are waiting for - so we must
* break out of the runloop frequently to check. We do this
* by setting a small delay and increasing it each time round
* so that this semi-busy wait doesn't consume too much
* processor time (I hope).
* We set an upper limit on the delay to avoid responsiveness
* problems.
*/
RELEASE(delay_date);
delay_date = [dateClass allocWithZone: NSDefaultMallocZone()];
if (delay_interval < 1.0)
{
next_interval = last_interval + delay_interval;
last_interval = delay_interval;
delay_interval = next_interval;
}
delay_date
= [delay_date initWithTimeIntervalSinceNow: delay_interval];
/*
* We must not set a delay date that is further in the future
* than the timeout date for the response to be returned.
*/
if ([timeout_date earlierDate: delay_date] == timeout_date)
{
limit_date = timeout_date;
}
else
{
limit_date = delay_date;
}
/*
* If the runloop returns without having done anything, AND we
* were waiting for the final timeout, then we must break out
* of the loop.
*/
if ([runLoop runMode: NSConnectionReplyMode
beforeDate: limit_date] == NO)
{
if (limit_date == timeout_date)
{
M_LOCK(_queueGate); isLocked = YES;
node = GSIMapNodeForKey(_replyMap, (GSIMapKey)sn);
break;
}
}
} }
else else
{ {
limit_date = delay_date; /*
} * Normal operation - wait for data or for a timeout.
*/
/* if ([runLoop runMode: NSConnectionReplyMode
* If the runloop returns without having done anything, AND we beforeDate: timeout_date] == NO)
* were waiting for the final timeout, then we must break out
* of the loop.
*/
if ([runLoop runMode: NSConnectionReplyMode
beforeDate: limit_date] == NO)
{
if (limit_date == timeout_date)
{ {
M_LOCK(_queueGate); M_LOCK(_queueGate); isLocked = YES;
node = GSIMapNodeForKey(_replyMap, (GSIMapKey)sn); node = GSIMapNodeForKey(_replyMap, (GSIMapKey)sn);
break; break;
} }
} }
M_LOCK(_queueGate); isLocked = YES;
}
if (node == 0)
{
rmc = nil;
} }
else else
{ {
/* rmc = node->value.obj;
* Normal operation - wait for data to be received or for a timeout. GSIMapRemoveKey(_replyMap, (GSIMapKey)sn);
*/ }
if ([runLoop runMode: NSConnectionReplyMode M_UNLOCK(_queueGate); isLocked = NO;
beforeDate: timeout_date] == NO) TEST_RELEASE(delay_date);
TEST_RELEASE(timeout_date);
if (rmc == nil)
{
[NSException raise: NSInternalInconsistencyException
format: @"no reply message available"];
}
if (rmc == dummyObject)
{
if (_isValid == YES)
{ {
M_LOCK(_queueGate); [NSException raise: NSPortTimeoutException
node = GSIMapNodeForKey(_replyMap, (GSIMapKey)sn); format: @"timed out waiting for reply"];
break; }
else
{
[NSException raise: NSPortTimeoutException
format: @"invalidated while awaiting reply"];
} }
} }
M_LOCK(_queueGate); if (addedRunLoop == YES)
}
if (node == 0)
{
rmc = nil;
}
else
{
rmc = node->value.obj;
GSIMapRemoveKey(_replyMap, (GSIMapKey)sn);
}
M_UNLOCK(_queueGate);
TEST_RELEASE(delay_date);
TEST_RELEASE(timeout_date);
if (rmc == nil)
{
[NSException raise: NSInternalInconsistencyException
format: @"no reply message available"];
}
if (rmc == dummyObject)
{
if (_isValid == YES)
{ {
[NSException raise: NSPortTimeoutException addedRunLoop = NO;
format: @"timed out waiting for reply"]; [self removeRunLoop: runLoop];
}
else
{
[NSException raise: NSPortTimeoutException
format: @"invalidated while awaiting reply"];
} }
} }
NS_HANDLER
{
if (addedRunLoop == YES)
{
addedRunLoop = NO;
[self removeRunLoop: runLoop];
}
if (isLocked == YES)
{
M_UNLOCK(_queueGate);
}
[localException raise];
}
NS_ENDHANDLER
NSDebugMLLog(@"NSConnection", @"Consuming reply RMC %d on %x", sn, self); NSDebugMLLog(@"NSConnection", @"Consuming reply RMC %d on %x", sn, self);
return rmc; return rmc;
} }
@ -2951,21 +2989,6 @@ static void callEncoder (DOContext *ctxt)
reserved: [_sendPort reservedSpaceLength]]; reserved: [_sendPort reservedSpaceLength]];
M_LOCK(_refGate); M_LOCK(_refGate);
/*
* If we have sent out a request on a run loop that we don't already
* know about, it must be on a new thread - so if we have multipleThreads
* enabled, we must add the run loop of the new thread so that we can
* get the reply in this thread.
*/
if (_multipleThreads == YES && needsReply == YES)
{
NSRunLoop *loop = [runLoopClass currentRunLoop];
if ([_runLoops indexOfObjectIdenticalTo: loop] == NSNotFound)
{
[self addRunLoop: loop];
}
}
/* /*
* We replace the code we have just used in the cache, and tell it not to * We replace the code we have just used in the cache, and tell it not to

View file

@ -325,8 +325,9 @@ GSRunLoopForThread(NSThread *t)
r = [NSRunLoop new]; r = [NSRunLoop new];
[d setObject: r forKey: key]; [d setObject: r forKey: key];
RELEASE(r); RELEASE(r);
if (t == nil || t == defaultThread) if (housekeeper == nil && (t == nil || t == defaultThread))
{ {
CREATE_AUTORELEASE_POOL (arp);
NSNotificationCenter *ctr; NSNotificationCenter *ctr;
NSNotification *not; NSNotification *not;
NSInvocation *inv; NSInvocation *inv;
@ -334,7 +335,7 @@ GSRunLoopForThread(NSThread *t)
ctr = [NSNotificationCenter defaultCenter]; ctr = [NSNotificationCenter defaultCenter];
not = [NSNotification notificationWithName: @"GSHousekeeping" not = [NSNotification notificationWithName: @"GSHousekeeping"
object: r object: nil
userInfo: nil]; userInfo: nil];
sel = @selector(postNotification:); sel = @selector(postNotification:);
inv = [NSInvocation invocationWithMethodSignature: inv = [NSInvocation invocationWithMethodSignature:
@ -344,10 +345,14 @@ GSRunLoopForThread(NSThread *t)
[inv setArgument: &not atIndex: 2]; [inv setArgument: &not atIndex: 2];
[inv retainArguments]; [inv retainArguments];
housekeeper = [NSTimer timerWithTimeInterval: 30.0 housekeeper = [[NSTimer alloc] initWithFireDate: nil
invocation: inv interval: 30.0
repeats: YES]; target: inv
selector: NULL
userInfo: nil
repeats: YES];
[r addTimer: housekeeper forMode: NSDefaultRunLoopMode]; [r addTimer: housekeeper forMode: NSDefaultRunLoopMode];
RELEASE(arp);
} }
} }
} }
@ -408,23 +413,17 @@ gnustep_base_thread_callback()
*/ */
+ (NSThread*) currentThread + (NSThread*) currentThread
{ {
NSThread *t; NSThread *t = nil;
if (entered_multi_threaded_state == NO) if (entered_multi_threaded_state == NO)
{ {
/* /*
* The NSThread class has been initialized - so we will have a default * The NSThread class has been initialized - so we will have a default
* thread set up. * thread set up unless the default thread subsequently exited.
*/ */
t = defaultThread; t = defaultThread;
if (t == nil)
{
fprintf(stderr, "ALERT ... [NSThread +currentThread] ... the "
"default thread is nil!");
fflush(stderr); // Needed for windoze
}
} }
else if (t == nil)
{ {
t = (NSThread*)objc_thread_get_data(); t = (NSThread*)objc_thread_get_data();
if (t == nil) if (t == nil)
@ -630,6 +629,10 @@ gnustep_base_thread_callback()
[NSAutoreleasePool _endThread: self]; [NSAutoreleasePool _endThread: self];
} }
} }
if (self == defaultThread)
{
defaultThread = nil;
}
NSDeallocateObject(self); NSDeallocateObject(self);
} }