diff options
Diffstat (limited to 'pr/src/md/mac/macthr.c')
-rw-r--r-- | pr/src/md/mac/macthr.c | 170 |
1 files changed, 150 insertions, 20 deletions
diff --git a/pr/src/md/mac/macthr.c b/pr/src/md/mac/macthr.c index 0df8a590..fd3b489d 100644 --- a/pr/src/md/mac/macthr.c +++ b/pr/src/md/mac/macthr.c @@ -39,7 +39,7 @@ #include <MacTypes.h> #include <Timer.h> #include <OSUtils.h> - +#include <Math64.h> #include <LowMem.h> #include <Multiprocessing.h> #include <Gestalt.h> @@ -49,6 +49,8 @@ TimerUPP gTimerCallbackUPP = NULL; PRThread * gPrimaryThread = NULL; +ProcessSerialNumber gApplicationProcess; + PR_IMPLEMENT(PRThread *) PR_GetPrimaryThread() { return gPrimaryThread; @@ -159,7 +161,21 @@ extern void _MD_ClearStack(PRThreadStack *ts) #pragma mark - #pragma mark TIME MANAGER-BASED CLOCK -TMTask gTimeManagerTaskElem; +// On Mac OS X, it's possible for the application to spend lots of time +// in WaitNextEvent, yielding to other applications. Since NSPR threads are +// cooperative here, this means that NSPR threads will also get very little +// time to run. To kick ourselves out of a WaitNextEvent call when we have +// determined that it's time to schedule another thread, the Timer Task +// (which fires every 8ms, even when other apps have the CPU) calls WakeUpProcess. +// We only want to do this on Mac OS X; the gTimeManagerTaskDoesWUP variable +// indicates when we're running on that OS. +// +// Note that the TimerCallback makes use of gApplicationProcess. We need to +// have set this up before the first possible run of the timer task; we do +// so in _MD_EarlyInit(). +static Boolean gTimeManagerTaskDoesWUP; + +static TMTask gTimeManagerTaskElem; extern void _MD_IOInterrupt(void); _PRInterruptTable _pr_interruptTable[] = { @@ -168,6 +184,8 @@ _PRInterruptTable _pr_interruptTable[] = { { 0 } }; +#define kMacTimerInMiliSecs 8L + pascal void TimerCallback(TMTaskPtr tmTaskPtr) { _PRCPU *cpu = _PR_MD_CURRENT_CPU(); @@ -184,8 +202,20 @@ pascal void TimerCallback(TMTaskPtr tmTaskPtr) // And tell nspr that a clock interrupt occured. _PR_ClockInterrupt(); - if ((_PR_RUNQREADYMASK(cpu)) >> ((_PR_MD_CURRENT_THREAD()->priority))) + if ((_PR_RUNQREADYMASK(cpu)) >> ((_PR_MD_CURRENT_THREAD()->priority))) { + if (gTimeManagerTaskDoesWUP) { + // We only want to call WakeUpProcess if we know that NSPR has managed to switch threads + // since the last call, otherwise we end up spewing out WakeUpProcess() calls while the + // application is blocking somewhere. This can interfere with events loops other than + // our own (see bug 158927). + if (UnsignedWideToUInt64(cpu->md.lastThreadSwitch) > UnsignedWideToUInt64(cpu->md.lastWakeUpProcess)) + { + WakeUpProcess(&gApplicationProcess); + cpu->md.lastWakeUpProcess = UpTime(); + } + } _PR_SET_RESCHED_FLAG(); + } _PR_FAST_INTSON(is); @@ -198,8 +228,10 @@ void _MD_StartInterrupts(void) { gPrimaryThread = _PR_MD_CURRENT_THREAD(); + gTimeManagerTaskDoesWUP = RunningOnOSX(); + if ( !gTimerCallbackUPP ) - gTimerCallbackUPP = NewTimerProc(TimerCallback); + gTimerCallbackUPP = NewTimerUPP(TimerCallback); // Fill in the Time Manager queue element @@ -222,26 +254,36 @@ void _MD_StopInterrupts(void) } } + +#define MAX_PAUSE_TIMEOUT_MS 500 + void _MD_PauseCPU(PRIntervalTime timeout) { if (timeout != PR_INTERVAL_NO_WAIT) { - EventRecord theEvent; - - /* - ** Calling WaitNextEvent() here is suboptimal. This routine should - ** pause the process until IO or the timeout occur, yielding time to - ** other processes on operating systems that require this (Mac OS classic). - ** WaitNextEvent() may incur too much latency, and has other problems, - ** such as the potential to drop suspend/resume events, and to handle - ** AppleEvents at a time at which we're not prepared to handle them. - */ - (void) WaitNextEvent(nullEvent, &theEvent, 1, NULL); - + // There is a race condition entering the critical section + // in AsyncIOCompletion (and probably elsewhere) that can + // causes deadlock for the duration of this timeout. To + // work around this, use a max 500ms timeout for now. + // See bug 99561 for details. + if (PR_IntervalToMilliseconds(timeout) > MAX_PAUSE_TIMEOUT_MS) + timeout = PR_MillisecondsToInterval(MAX_PAUSE_TIMEOUT_MS); + + WaitOnIdleSemaphore(timeout); (void) _MD_IOInterrupt(); } } +void _MD_InitRunningCPU(_PRCPU* cpu) +{ + cpu->md.trackScheduling = RunningOnOSX(); + if (cpu->md.trackScheduling) { + AbsoluteTime zeroTime = {0, 0}; + cpu->md.lastThreadSwitch = UpTime(); + cpu->md.lastWakeUpProcess = zeroTime; + } +} + //############################################################################## //############################################################################## @@ -528,19 +570,25 @@ void _MD_SetIntsOff(PRInt32 ints) #pragma mark - #pragma mark CRITICAL REGION SUPPORT + +static PRBool RunningOnOSX() +{ + long systemVersion; + OSErr err = Gestalt(gestaltSystemVersion, &systemVersion); + return (err == noErr) && (systemVersion >= 0x00001000); +} + + #if MAC_CRITICAL_REGIONS MDCriticalRegionID gCriticalRegion; void InitCriticalRegion() { - long systemVersion; OSStatus err; // we only need to do critical region stuff on Mac OS X - err = Gestalt(gestaltSystemVersion, &systemVersion); - gUseCriticalRegions = (err == noErr) && (systemVersion >= 0x00001000); - + gUseCriticalRegions = RunningOnOSX(); if (!gUseCriticalRegions) return; err = MD_CriticalRegionCreate(&gCriticalRegion); @@ -586,3 +634,85 @@ void LeaveCritialRegion() #endif // MAC_CRITICAL_REGIONS +//############################################################################## +//############################################################################## +#pragma mark - +#pragma mark IDLE SEMAPHORE SUPPORT + +/* + Since the WaitNextEvent() in _MD_PauseCPU() is causing all sorts of + headache under Mac OS X we're going to switch to MPWaitOnSemaphore() + which should do what we want +*/ + +#if TARGET_CARBON +PRBool gUseIdleSemaphore = PR_FALSE; +MPSemaphoreID gIdleSemaphore = NULL; +#endif + +void InitIdleSemaphore() +{ + // we only need to do idle semaphore stuff on Mac OS X +#if TARGET_CARBON + gUseIdleSemaphore = RunningOnOSX(); + if (gUseIdleSemaphore) + { + OSStatus err = MPCreateSemaphore(1 /* max value */, 0 /* initial value */, &gIdleSemaphore); + PR_ASSERT(err == noErr); + } +#endif +} + +void TermIdleSemaphore() +{ +#if TARGET_CARBON + if (gUseIdleSemaphore) + { + OSStatus err = MPDeleteSemaphore(gIdleSemaphore); + PR_ASSERT(err == noErr); + gUseIdleSemaphore = NULL; + } +#endif +} + + +void WaitOnIdleSemaphore(PRIntervalTime timeout) +{ +#if TARGET_CARBON + if (gUseIdleSemaphore) + { + OSStatus err = MPWaitOnSemaphore(gIdleSemaphore, kDurationMillisecond * PR_IntervalToMilliseconds(timeout)); + PR_ASSERT(err == noErr); + } + else +#endif + { + EventRecord theEvent; + /* + ** Calling WaitNextEvent() here is suboptimal. This routine should + ** pause the process until IO or the timeout occur, yielding time to + ** other processes on operating systems that require this (Mac OS classic). + ** WaitNextEvent() may incur too much latency, and has other problems, + ** such as the potential to drop suspend/resume events. + */ + (void)WaitNextEvent(nullEvent, &theEvent, 1, NULL); + } +} + + +void SignalIdleSemaphore() +{ +#if TARGET_CARBON + if (gUseIdleSemaphore) + { + // often we won't be waiting on the semaphore here, so ignore any errors + (void)MPSignalSemaphore(gIdleSemaphore); + } + else +#endif + { + WakeUpProcess(&gApplicationProcess); + } +} + + |