This is ThreadedApp.m in view mode; [Download] [Up]
/*
* ThreadedApp: adds support for a multi-threaded application.
* (c) 1997 Chris Roehrig <croehrig@House.ORG>
*/
#import "ThreadedApp.h"
#import <objc/objc-runtime.h>
@implementation ThreadedApp : Application
// export global to identify the main thread...
cthread_t mainThread;
/*****************************************************************
* Basic Thread support
*/
// simple "function control block" to invoke a method
struct _fcb {
IMP function;
id target;
SEL selector;
any_t arg;
mutex_t lock;
condition_t cond;
volatile BOOL done;
};
static void method_dispatcher( struct _fcb *fcb )
{
struct _fcb myfcb;
// lock the fcb...
mutex_lock( fcb->lock );
// copy the args from parent's stack-based struct...
// (ignore the lock and cond; they are only copied by reference)
myfcb = *fcb;
fcb->done =YES;
// let parent know it's ok to exit and destroy my args
condition_signal( fcb->cond );
mutex_unlock( fcb->lock );
// call the method...
myfcb.function( myfcb.target, myfcb.selector, myfcb.arg );
}
- (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget
withObject:(id)anArgument
{
// allocate all data off stack...
struct _fcb fcb;
struct mutex m;
struct condition c;
fcb.function = [aTarget methodFor:aSelector];
if( fcb.function ){
fcb.target = aTarget;
fcb.selector = aSelector;
fcb.arg = (any_t) anArgument;
mutex_init( &m );
fcb.lock = &m;
condition_init( &c );
fcb.cond = &c;
fcb.done = NO;
mutex_lock( &m );
cthread_detach(
cthread_fork((any_t(*)())method_dispatcher,(any_t)&fcb) );
// wait for thread to copy its args before exiting and
// destroying them!
while( !fcb.done )
condition_wait( &c, &m );
mutex_unlock( &m );
mutex_clear( &m );
condition_clear( &c );
}
}
- (void)exitCurrentThread
{
cthread_exit(0);
}
- (void)sleepCurrentThread:(int)msec
{
thread_switch(THREAD_NULL, SWITCH_OPTION_WAIT, msec);
}
/*****************************************************************
* Callback support
*
* Callbacks are done by sending the main AppKit thread a Mach message
* requesting it to invoke a method.
*
* The callback Mach message is faked as a simple message
* (i.e. no out-of-line data), since the message never crosses task/memory
* spaces, and so memory references are still valid.
*
* The Mach message handler is invoked by the DPSGetEvent routine
* called by the Application's event loop. It monitors the port and
* calls the handler when messages arrive on it. If DPSGetEvent
* (or DPSPeekEvent) is NOT being called, the messages will not be
* handled. The AppKit thread MUST remain responsive to events in order
* for messages to be handled.
*
* For fast callbacks, the Mach message handler invokes the callback method
* directly. The DPSGetEvent routine is not reentrant and so the callback
* method cannot consume events. In particular, this means it cannot
* use Locks since they use event loops (see Locks, below). The callback
* method can be blocking (wait) or non-blocking.
*
* For safe and slow callbacks, the Mach message handler does not invoke the
* callback method directly. Instead, it posts an (NX_APPDEFINED)
* EV_CALLBACK event to the Application's event queue which gets handled
* by the Application's applicationDefined: method. This is to allow the
* called-back method to run modal event loops itself. (In particular, this
* is necessary if the called-back method uses locks to communicate with
* other threads (see Locks, below).
* The DPSGetEvent routine is not reentrant, so it is not possible for
* the message handler to eventually call DPSGetEvent again. By posting
* an event to the event queue, the message handler returns, DPSGetEvent
* returns, and the EV_CALLBACK event is processed by the Application's
* event loop, as if it is any other event. This allows the called-back
* method to have full access to the AppKit's features and to run its
* own event loop if it desires.
*
* Locks
* It is possible for the main (AppKit) thread to use Locks and
* ConditionLocks to synchronize with its other threads. However, it
* is forbidden for these Locks to block since that would prevent messages
* from being received and may cause other threads to block waiting to
* send messages to the main thread, potentially resulting in deadlock.
* The AppKit thread MUST remain responsive to events!
*
* The CJRLock and CJRConditionLock objects handle the case where
* the main AppKit thread acquires a lock. They do this by
* testing to see if they can acquire the lock (mutex_try_lock). If
* they can, they've succeeded. If not, they start a modal loop getting
* NX_APPDEFINED events, and looking for EV_UNLOCK events. When one is
* received, this indicates that another thread has unlocked a mutex.
* and we try to acquire the lock again. This loops until success.
* This is a sort of non-blocking "block" since other events aren't
* processed during the block (they remain on the queue, however).
* The CJRLock's unlock methods send the required EV_UNLOCK messages.
*
* In addition, EV_CALLBACK events are continued to be processed during
* this "blocking" modal loop. This has some bizarre consequences:
* Another callback method can be dispatched and preempt a callback method
* that is currently blocked waiting for lock to come free. This
* preemptive callback method can now also "block" waiting for a lock.
* The ThreadedApp's lock methods are re-entrant, so this doesn't pose
* a problem. As in all threaded programming, you should not rely
* on the order that callbacks might occur.
*
*/
// data for wait callbacks
struct waitdata {
struct mutex lock; // statically allocated
struct condition cond;
volatile id retval;
volatile BOOL done;
};
// thresholds for messages
#define FAST_THRESHOLD (NX_MODALRESPTHRESHOLD+1)
#define BASE_THRESHOLD NX_BASETHRESHOLD
// types and options for our Mach messages and events...
// message/event type (0-255)...
#define EV_UNLOCK 1 // threadDidUnlock event
#define EV_CALLBACK 2 // callback event
// message/event options (bit set)...
#define EV_CALLBACK_ARG 0x0100 // callback method takes an arg
#define EV_CALLBACK_WAIT 0x0200 // block and wait for method to return
#define EV_CALLBACK_POST 0x0400 // post the callback as an event
// callback message...
#define CALLBACK_DATA 4
typedef struct {
msg_header_t h;
msg_type_t t;
id target;
SEL sel;
id arg;
struct waitdata *data;
} callbackMsg;
// unlock message...
#define UNLOCK_DATA 1
typedef struct {
msg_header_t h;
msg_type_t t;
id lock;
} unlockMsg;
// global variables...
static BOOL blocked; // YES if the app is "blocked" in an event loop
// waiting for an appropriate EV_UNLOCK event
static int block_count; // number of nested blocks (diagnostic)
static BOOL fastPending; // YES if a fast callback is in progress
// callback ports
static port_t fastPort, basePort;
static void post_unlock_event( unlockMsg *msg )
{
NXEvent ev;
ev.type = NX_APPDEFINED;
ev.location.x = 0;
ev.location.y = 0;
ev.time = 0L;
ev.flags = 0;
ev.window = 0;
ev.data.compound.subtype = msg->h.msg_id;
ev.data.compound.misc.L[0] = (long) msg->lock; /* the lock object */
ev.data.compound.misc.L[1] = 0; /* unused */
ev.ctxt = [NXApp context];
if( DPSPostEvent( &ev, FALSE ) != 0 ){
printf( "handle_unlock: DPSPostEvent cannot post EV_UNLOCK\n" );
}
}
static void post_callback_event( callbackMsg *msg )
{
NXEvent ev;
/*
* ok, uck. I've got 4 int-sized parms to send and here they go:
* target goes in L[0]
* data goes in L[1]
* sel goes in flags
* arg goes in window
*
* Doesn't matter what they are, since the window-manager didn't make
* the message anyways.
*/
ev.type = NX_APPDEFINED;
ev.location.x = 0;
ev.location.y = 0;
ev.time = 0L;
ev.flags = (int) msg->sel;
ev.window = (int) msg->arg;
ev.data.compound.subtype = msg->h.msg_id;
ev.data.compound.misc.L[0] = (long) msg->target;
ev.data.compound.misc.L[1] = (long) msg->data;
ev.ctxt = [NXApp context];
if( DPSPostEvent( &ev, FALSE ) != 0 ){
printf( "handle_callback: DPSPostEvent cannot post EV_CALLBACK\n" );
}
}
static void do_callback( callbackMsg *msg )
{
int options;
id ret;
struct waitdata *d;
options = msg->h.msg_id & 0xff00;
if( options & EV_CALLBACK_WAIT ){
d = msg->data;
mutex_lock( &d->lock );
}
fastPending = YES;
if( options & EV_CALLBACK_ARG ){
ret = [msg->target perform:msg->sel with:msg->arg];
} else {
ret = [msg->target perform:msg->sel];
}
fastPending = NO;
if( options & EV_CALLBACK_WAIT ){
d->retval = ret;
d->done = YES;
condition_signal( &d->cond );
mutex_unlock( &d->lock );
}
}
static void callbackHandler( msg_header_t *msg )
/*
* This is called from DPSGetEvent or DPSPeekEvent if there
* is a message on the queue.
* This handler converts the Mach message to the appropriate
* NXEvent and posts it to the event queue (similar to what the
* NXTrackingTimer does).
* For fast callbacks, it invokes the method directly.
*/
{
int msg_type, msg_options;
#if 0
putchar('*');
fflush(stdout);
#endif
msg_type = msg->msg_id & 0x00ff;
msg_options = msg->msg_id & 0xff00;
switch( msg_type ){
case EV_UNLOCK:
// no point sending UNLOCK events if no one cares...
if( blocked ){
post_unlock_event( (unlockMsg *)msg );
}
break;
case EV_CALLBACK:
if( msg_options & EV_CALLBACK_POST ){
// callbacks posted as events...
post_callback_event( (callbackMsg *)msg );
} else {
// fast callbacks; do them here...
do_callback( (callbackMsg *)msg );
}
break;
default:
printf( "unknown message: 0x04%x\n", msg->msg_id );
break;
}
}
- (NXEvent*)getNextEvent:(int)mask waitFor:(double)timeout threshold:(int)level
/*
* Override the master NXApp event getter to get and dispatch NX_APPDEFINED
* events if they aren't already included in the mask. This allows
* callbacks and EV_UNLOCK events to get through unsuspecting modal loops
* such as for handling mouse-down events.
*/
{
NXEvent *ev;
if( mask & NX_APPDEFINEDMASK ){
// caller must know what they're doing...
return [super getNextEvent:mask waitFor:timeout threshold:level];
}
mask |= NX_APPDEFINEDMASK;
for(;;){
ev = [super getNextEvent:mask waitFor:timeout threshold:level];
if( ev->type != NX_APPDEFINED ) break;
[self applicationDefined:ev];
}
return ev;
}
- (NXEvent*)getNextEvent:(int)mask
{
NXEvent *ev;
if( mask & NX_APPDEFINEDMASK ){
// caller must know what they're doing...
return [super getNextEvent:mask ];
}
mask |= NX_APPDEFINEDMASK;
for(;;){
ev = [super getNextEvent:mask];
if( ev->type != NX_APPDEFINED ) break;
[self applicationDefined:ev];
}
return ev;
}
- applicationDefined:(NXEvent *)ev
/*
* This method receives the NX_APPDEFINED events dispatched by the
* Application's run: and sendEvent: methods (but not by other
* AppKit modal loops: Button, Slider, etc.)
* This is where we perform the slow callbacks.
*/
{
id ret;
int ev_type, ev_options;
id target, arg;
SEL sel;
struct waitdata *d;
ev_type = ev->data.compound.subtype & 0x00ff;
ev_options = ev->data.compound.subtype & 0xff00;
switch( ev_type ){
case EV_UNLOCK:
// Don't need this now; must be left on the queue from a
// previous lock: modal loop below...
// printf( "applicationDefined: Received a rogue EV_UNLOCK!\n" );
break;
case EV_CALLBACK:
/* uck. See handle_callback above */
target = (id) ev->data.compound.misc.L[0];
sel = (SEL) ev->flags;
arg = (id) ev->window;
if( ev_options & EV_CALLBACK_WAIT ){
d = (struct waitdata *)ev->data.compound.misc.L[1];
mutex_lock( &d->lock );
}
// The event data in ev can be invalidated by this method
// invocation if it recursively calls DPSGetEvent.
// That's ok; we've now got a local copy of everything we need.
if( ev_options & EV_CALLBACK_ARG ){
ret = [target perform:sel with:arg];
} else {
ret = [target perform:sel];
}
if( ev_options & EV_CALLBACK_WAIT ){
d->retval = ret;
d->done = YES;
condition_signal( &d->cond );
mutex_unlock( &d->lock );
}
break;
default:
printf( "applicationDefined: Unknown event: %d\n",
ev->data.compound.subtype );
break;
}
return self;
}
/*
* The "designated" callback function.
*/
// option bits for the master callback method...
#define WAIT 0x02
#define ARG 0x08
#define HIGH 0x01
#define POST 0x04
#define FAST HIGH
#define SAFE (HIGH|POST)
#define SLOW POST
- (id)callback:target perform:(SEL)aSel with:anObject opt:(int)option
{
// allocate all variables off stack so it's reentrant and thread-safe
callbackMsg msg;
struct waitdata d;
// if this is called from the main thread, just do a perform:
// otherwise 'wait' may deadlock
if( cthread_self() == mainThread ){
if( option & ARG ){
return [target perform:aSel with:anObject];
} else {
return [target perform:aSel];
}
}
// setup header
msg.h.msg_simple = TRUE;
msg.h.msg_size = sizeof(callbackMsg);
msg.h.msg_local_port = PORT_NULL;
msg.h.msg_remote_port = (option&HIGH) ? fastPort : basePort;
msg.h.msg_type = MSG_TYPE_NORMAL;
msg.t.msg_type_name = MSG_TYPE_INTEGER_32;
msg.t.msg_type_size = 32;
msg.t.msg_type_number = CALLBACK_DATA;
msg.t.msg_type_inline = TRUE;
msg.t.msg_type_longform = FALSE;
msg.t.msg_type_deallocate = FALSE;
// set up message data
msg.h.msg_id = EV_CALLBACK;
msg.target = target;
msg.sel = aSel;
// set options...
if( option & POST ){
msg.h.msg_id |= EV_CALLBACK_POST;
}
if( option & ARG ){
msg.h.msg_id |= EV_CALLBACK_ARG;
msg.arg = anObject;
}
if( option & WAIT ){
msg.h.msg_id |= EV_CALLBACK_WAIT;
msg.data = &d;
d.done = NO;
mutex_init( &d.lock );
condition_init( &d.cond );
mutex_lock( &d.lock );
}
msg_send((msg_header_t *)&msg, MSG_OPTION_NONE,0);
if( option & WAIT ){
// wait for method to return
while( !d.done ){
condition_wait( &d.cond, &d.lock );
}
mutex_unlock( &d.lock );
mutex_clear( &d.lock );
condition_clear( &d.cond );
return d.retval;
} else {
return nil;
}
}
// Exported variations on the theme
- (id)callbackAndWaitTarget:target perform:(SEL)aSel
{
return [self callback:target perform:aSel with:nil opt:FAST|WAIT];
}
- (id)callbackAndWaitTarget:target perform:(SEL)aSel with:anObject
{
return [self callback:target perform:aSel with:anObject opt:FAST|WAIT|ARG];
}
- (void)callbackTarget:target perform:(SEL)aSel
{
[self callback:target perform:aSel with:nil opt:FAST];
}
- (void)callbackTarget:target perform:(SEL)aSel with:anObject
{
[self callback:target perform:aSel with:anObject opt:FAST|ARG];
}
- (id)safeCallbackAndWaitTarget:target perform:(SEL)aSel
{
return [self callback:target perform:aSel with:nil opt:SAFE|WAIT];
}
- (id)safeCallbackAndWaitTarget:(id)target perform:(SEL)aSel with:(id)anObject
{
return [self callback:target perform:aSel with:anObject opt:SAFE|WAIT|ARG];
}
- (void)safeCallbackTarget:target perform:(SEL)aSel
{
[self callback:target perform:aSel with:nil opt:SAFE];
}
- (void)safeCallbackTarget:(id)target perform:(SEL)aSel with:(id)anObject
{
[self callback:target perform:aSel with:anObject opt:SAFE|ARG];
}
- (id)slowCallbackAndWaitTarget:target perform:(SEL)aSel
{
return [self callback:target perform:aSel with:nil opt:SLOW|WAIT];
}
- (id)slowCallbackAndWaitTarget:(id)target perform:(SEL)aSel with:(id)anObject
{
return [self callback:target perform:aSel with:anObject opt:SLOW|WAIT|ARG];
}
- (void)slowCallbackTarget:target perform:(SEL)aSel
{
[self callback:target perform:aSel with:nil opt:SLOW];
}
- (void)slowCallbackTarget:(id)target perform:(SEL)aSel with:(id)anObject
{
[self callback:target perform:aSel with:anObject opt:SLOW|ARG];
}
+ new
{
[super new];
// register ports for receiving callback messages
port_allocate( task_self(), &fastPort );
DPSAddPort( fastPort, (DPSPortProc)callbackHandler,
sizeof(callbackMsg), NULL, FAST_THRESHOLD );
port_allocate( task_self(), &basePort );
DPSAddPort( basePort, (DPSPortProc)callbackHandler,
sizeof(callbackMsg), NULL, BASE_THRESHOLD );
// identify the main Thread
mainThread = cthread_self();
// set runtime to be thread-safe...
objc_setMultithreaded(YES);
return NXApp;
}
- free
{
DPSRemovePort(fastPort);
DPSRemovePort(basePort);
port_deallocate( task_self(), fastPort );
port_deallocate( task_self(), basePort );
return [super free];
}
@end
/*****************************************************************
* Support for CJRLocks...
*/
@implementation ThreadedApp (CJRLock)
- (void)threadDidUnlock:sender
// construct a EV_UNLOCK Mach message to send to fastPort...
{
unlockMsg msg;
if( !blocked ){
// no point in sending UNLOCK events if no one cares...
return;
}
// setup header
msg.h.msg_simple = TRUE;
msg.h.msg_size = sizeof(unlockMsg);
msg.h.msg_local_port = PORT_NULL;
msg.h.msg_remote_port = fastPort;
msg.h.msg_type = MSG_TYPE_NORMAL;
// type field for zero data...
msg.t.msg_type_name = MSG_TYPE_INTEGER_32;
msg.t.msg_type_size = 32;
msg.t.msg_type_number = UNLOCK_DATA;
msg.t.msg_type_inline = TRUE;
msg.t.msg_type_longform = FALSE;
msg.t.msg_type_deallocate = FALSE;
// message type...
msg.h.msg_id = EV_UNLOCK;
msg.lock = sender; // the lock object that unlocked
msg_send((msg_header_t *)&msg, MSG_OPTION_NONE,0);
}
- (void)lock: (mutex_t)mutex
/*
* This method is called from CJRLock and CJRConditionLock methods
* and should only be invoked from the main thread.
* It is possible for this routine to be called recursively in response
* to servicing applicationDefined:
*/
{
NXEvent *ev;
int saved_block;
if( fastPending ){
// this doesn't return
[self error: "Cannot use locks from a fast callback method\n"];
}
if( !mutex_try_lock(mutex) ){
saved_block = blocked;
block_count++;
blocked = YES;
for(;;){
ev = [self getNextEvent:NX_APPDEFINEDMASK
waitFor:NX_FOREVER
threshold:FAST_THRESHOLD];
if( ev->data.compound.subtype == EV_UNLOCK ){
if( mutex_try_lock(mutex) ) break;
} else {
// handle the event...
[self applicationDefined:ev ];
}
}
blocked = saved_block;
block_count--;
}
}
- (void) lock:(mutex_t)mutex whenCondition:(condition_t)cond
withData:(volatile int *)data is:(int)value
/*
* This method is called from CJRConditionLock methods
* and should only be invoked from the main thread.
* It is possible for this routine to be called recursively in response
* to servicing applicationDefined:
*/
{
NXEvent *ev;
int saved_block;
if( fastPending ){
// this doesn't return
[self error: "Cannot use locks from a fast callback method\n"];
}
if( mutex_try_lock(mutex) ){
if( *data == value ) {
return;
}
mutex_unlock(mutex);
}
saved_block = blocked;
block_count++;
blocked = YES;
for(;;){
ev = [self getNextEvent:NX_APPDEFINEDMASK
waitFor:NX_FOREVER
threshold:FAST_THRESHOLD];
if( ev->data.compound.subtype == EV_UNLOCK ){
if( mutex_try_lock(mutex) ){
if( *data == value ) break;
mutex_unlock(mutex);
}
} else {
// handle the event...
[self applicationDefined:ev ];
}
}
blocked = saved_block;
block_count--;
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.