summaryrefslogtreecommitdiff
path: root/pr/src/md/mac/macthr.c
diff options
context:
space:
mode:
Diffstat (limited to 'pr/src/md/mac/macthr.c')
-rw-r--r--pr/src/md/mac/macthr.c170
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);
+ }
+}
+
+