WebObjects 4: Issues with Applications Using Enterprise Objects from Multiple Threads

Objective-C applications written for WebObjects 4.0 and WebObjests 4.5 that use the Enterprise Objects Framework from more than one thread and whose threads do not run indefinitely may on rare occasions raise exceptions, damage memory, and cause memory faults.
Symptoms
These symptoms may occur very rarely, and a given application may exhibit different symptoms at different times.


Solution

1. Add to your project a file EOFThreadWorkaround.h containing the following:


//  EOFThreadWorkaround.h
//  Copyright (c) 2002 Apple Computer, Inc. All rights reserved.

#import <EOControl/EOControl.h>

@interface EOObserverCenter (ThreadWorkaround)

//  preventCertainExceptionsOnThreadExit -- Invoke this once, at startup, before doing any EOF operations, to work
//  around certain problems with multi-threaded EOF applications.

+ (void) preventCertainExceptionsOnThreadExit;

//  changeThreadCache -- Any thread which does EOF operations but which is NOT created with NSThread should
//  invoke this just before exiting.

+ (void) changeThreadCache;

@end

2. Add to your project a file EOFThreadWorkaround.m containing the following:


//  EOFThreadWorkaround.m
//  Copyright (c) 2002 Apple Computer, Inc. All rights reserved.

#import "EOFThreadWorkaround.h"

@implementation EOObserverCenter (ThreadWorkaround)

static NSConditionLock  *threadFixLock = nil;

//  Conditions for the above lock
#define CONDITION_VALID             1         // idle state
#define CONDITION_NEEDCHANGE        2   // need some other thread to change the cache for us


+ (void) _changeThreadCacheWhenNeeded;
{
     [[NSAutoreleasePool alloc] init];

     //  Loop forever, waiting for other threads to signal that they need the cache changed.
     //  When they do, query the suppress count, which gets OUR thread into the cache.
     //  Since our thread stays valid forever, this makes the cache valid, important when
     //  other threads are exiting.

     while (1)
     {
         [threadFixLock lockWhenCondition: CONDITION_NEEDCHANGE];    // wait 'til we're needed
         [EOObserverCenter observerNotificationSuppressCount];
         [threadFixLock unlockWithCondition: CONDITION_VALID];       // and tell the world we're done
     }
}

+ (void) preventCertainExceptionsOnThreadExit;
{
     static BOOL beenHere = NO;

     if (beenHere) return;                                           // do this only once
     beenHere = YES;

     //  Create the lock to let any thread communicate with the cache-fixing thread
     threadFixLock = [[NSConditionLock alloc] initWithCondition: CONDITION_VALID];

     //  Spawn the cache-fixing thread
     [NSThread detachNewThreadSelector :@selector(_changeThreadCacheWhenNeeded)  toTarget: self  withObject: nil];

     //  When any thread is about to exit, change the cache.
     [[NSNotificationCenter defaultCenter] addObserver: self
         selector:@selector(changeThreadCache)
         name:NSThreadWillExitNotification
         object: nil];
}

+ (void) changeThreadCache;
{
     //  Grab the lock so we know the other thread isn't in the middle of things
     //  Then unlock it, to make it do its change-thing
     [threadFixLock lockWhenCondition: CONDITION_VALID];             // wait 'til we can jump in
     [threadFixLock unlockWithCondition: CONDITION_NEEDCHANGE];      // and tell helper thread to do its thing

     //  Wait for it to do its thing
     //  Then unlock it without changing things
     [threadFixLock lockWhenCondition: CONDITION_VALID];             // wait for it to do its thing
     [threadFixLock unlock];
}

@end

3. Change your application to include the following, invoking the method only once at startup (in the main() function or in your application's initialization):


#import "EOFThreadWorkaround.h"
...
[EOObserverCenter preventCertainExceptionsOnThreadExit]; // one-time code to set up workaround

4. If your application or any third-party libraries in your application spawn any threads that use EOF but do not exit with [NSThread exit], invoke the following method just before exiting the thread.


[EOObserverCenter changeThreadCache]; // make sure our thread isn't in EOF's cache

This step is critical to working around the problem -- step 3 covers only threads that exit with [NSThread exit], but you must make sure that every thread that uses EOF is corrected. This step handles threads that do not exit with NSThread's method.

Published Date: Feb 18, 2012