4 * Routines for Macintosh pre-emptive threading system
6 * Portable Windows Library
8 * Copyright (c) 1993-1998 Equivalence Pty. Ltd.
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
20 * The Original Code is Portable Windows Library.
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
24 * Portions are Copyright (C) 1993 Free Software Foundation, Inc.
25 * All Rights Reserved.
27 * Contributor(s): ______________________________________.
30 * Revision 1.4 2002/06/27 06:38:58 robertj
31 * Changes to remove memory leak display for things that aren't memory leaks.
33 * Revision 1.3 2002/02/19 07:40:59 rogerh
34 * Remove PMutex destructor for Carbon.
36 * Revision 1.2 2002/02/19 07:28:02 rogerh
37 * PXAbortIO -> PXAbortBlock. Submitted by Peter Johnson <paj@mac.com>
39 * Revision 1.1 2001/08/11 15:38:43 rogerh
40 * Add Mac OS Carbon changes from John Woods <jfw@jfwhome.funhouse.com>
44 #include <sys/resource.h>
45 #include <new> // just because I want to throw std::bad_alloc...
49 extern int debug_mpthreads
;
52 PDECLARE_CLASS(PHouseKeepingThread
, PThread
)
55 : PThread(1000, NoAutoDeleteThread
, NormalPriority
, "Housekeeper")
56 { closing
= FALSE
; Resume(); }
59 void SetClosing() { closing
= TRUE
; }
69 int PThread::PXBlockOnIO(int handle
, int type
, const PTimeInterval
& timeout
)
71 //PTRACE(1,"PThread::PXBlockOnIO(" << handle << ',' << type << ')');
73 // make sure we flush the buffer before doing a write
74 fd_set tmp_rfd
, tmp_wfd
, tmp_efd
;
75 fd_set
* read_fds
= &tmp_rfd
;
76 fd_set
* write_fds
= &tmp_wfd
;
77 fd_set
* exception_fds
= &tmp_efd
;
79 struct timeval
* tptr
= NULL
;
80 struct timeval timeout_val
;
81 if (timeout
!= PMaxTimeInterval
) {
82 static const PTimeInterval
oneDay(0, 0, 0, 0, 1);
83 if (timeout
< oneDay
) {
84 timeout_val
.tv_usec
= (timeout
.GetMilliSeconds() % 1000) * 1000;
85 timeout_val
.tv_sec
= timeout
.GetSeconds();
96 FD_ZERO (exception_fds
);
99 case PChannel::PXReadBlock
:
100 case PChannel::PXAcceptBlock
:
101 FD_SET (handle
, read_fds
);
103 case PChannel::PXWriteBlock
:
104 FD_SET (handle
, write_fds
);
106 case PChannel::PXConnectBlock
:
107 FD_SET (handle
, write_fds
);
108 FD_SET (handle
, exception_fds
);
111 PAssertAlways(PLogicError
);
115 // include the termination pipe into all blocking I/O functions
116 int width
= handle
+1;
117 FD_SET(unblockPipe
[0], read_fds
);
118 width
= PMAX(width
, unblockPipe
[0]+1);
120 retval
= ::select(width
, read_fds
, write_fds
, exception_fds
, tptr
);
122 if ((retval
>= 0) || (errno
!= EINTR
))
126 if ((retval
== 1) && FD_ISSET(unblockPipe
[0], read_fds
)) {
128 ::read(unblockPipe
[0], &ch
, 1);
131 //PTRACE(1,"Unblocked I/O");
137 void PThread::PXAbortBlock() const
140 ::write(unblockPipe
[1], &ch
, 1);
144 // For Mac OS, the housekeeping thread has two jobs:
145 // First, poll for synchronous signals (as is traditional), and
146 // second, to poll the MPThread termination notification queue and clean up
147 // deceased PThreads.
148 // There is an ickiness here which depends on a current restriction of
149 // Mac OS X: synchronous signals (i.e. not signals resulting from
150 // exceptions) are only delivered to the main thread. I assume that
151 // it is therefore safe for the main thread to call MPNotifyQueue from
152 // a signal handler if and only if the main thread never calls MPNotifyQueue
153 // on the termination notification queue from its main code. This ought to
154 // be acceptable if notifying a queue is single-threaded per queue; if
155 // MPNotifyQueue has a global critical section, this will work very badly.
157 static MPQueueID terminationNotificationQueue
= 0;
158 // This bites. Threads don't know what process they come from (even though
159 // there can be only one), yet when we're winding down the process thread
160 // kills the housekeeper before it can clean up all the other threads.
161 // So the process thread has to poll the termination queue, but it does so
162 // from PThread context, so it can't know there's no housekeeper. yuck.
163 static BOOL noHousekeeper
= 0;
165 static void SetUpTermQueue()
168 // Let us PLEASE try not to get into any "create/delete" loops here.
169 // Create it and be DONE with it.
170 while (!terminationNotificationQueue
) {
172 err
= MPCreateQueue(&tempQueue
);
173 PAssert(err
== noErr
, "MPCreateQueue failed");
174 // When Motorola finally finishes the 620, there's a lot of Mac code
175 // gonna need to be rewritten.
176 // HAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHA!!!
177 // "finishes the 620". hee hee hee!
178 if (!OTCompareAndSwap32(0, (UInt32
)tempQueue
,
179 (UInt32
*)&terminationNotificationQueue
)) {
180 // then someone else snuck in and initialized it.
181 MPDeleteQueue(tempQueue
);
185 fprintf(stderr
,"set up notification queue\n");
187 // XXX MPNotifyQueue is perfectly willing to allocate memory
188 // XXX for the items dropped in the queue. However, if it can't,
189 // XXX then life just goes on -- and we miss a thread exit message.
190 // XXX If we reserve queue space, however, then we guarantee two
191 // XXX things: 1, we absolutely will be able to receive N
192 // XXX notifications, and 2, we absolutely will drop the N+1st
193 // XXX on the floor, spare memory or no. The target applications
194 // XXX for this library do not appear (currently) to generate
195 // XXX absurd numbers of threads, so I'll reserve an absurd number
196 // XXX of messages, and pretend that nothing can go wrong.
197 // XXX n go wrong.n go wrong.n go wrong.n go wrong.n go wrong.
198 // XXX If the following fails, it's probably for lack of memory,
199 // XXX in which case the queue will just try dynamic allocation.
200 (void)MPSetQueueReserve(terminationNotificationQueue
, 128);
205 static BOOL
PollNotificationQueue(Duration timeout
)
207 OSStatus err
= noErr
;
208 void *parm1
, *parm2
, *parm3
;
210 err
= MPWaitOnQueue(terminationNotificationQueue
,
211 &parm1
, &parm2
, &parm3
,
214 // then we got a notification
215 if ((int)parm1
== 1) {
216 // then it was a thread death notification, parm2 is
217 // the PThread pointer
220 fprintf(stderr
,"notified of %p death\n", parm2
);
222 PThread::PX_ThreadEnd(parm2
);
223 } // else parm1 == 0 and it's just a wakeup notice
228 void PHouseKeepingThread::Main()
230 PProcess
& process
= PProcess::Current();
235 PTimeInterval waitTime
= process
.timers
.Process();
238 if (waitTime
== PMaxTimeInterval
)
239 timeout
= kDurationForever
;
241 // "Values of type Duration are 32 bits long. They are intepreted
242 // in a manner consistend with the Time Manager -- positive values
243 // are in units of milliseconds, negative values are in units of
245 // Fortunately, PMaxTimeInterval is limited to a positive 32-bit
246 // number of milliseconds.
247 timeout
= (long)waitTime
.GetMilliSeconds();
250 // Block on the notification queue
252 (void)PollNotificationQueue(timeout
);
254 // whether we timed out or got notified, check the signals.
255 process
.PXCheckSignals();
260 fprintf(stderr
,"housekeeper exiting\n");
265 void PProcess::Construct()
267 // set the file descriptor limit to something sensible
269 PAssertOS(getrlimit(RLIMIT_NOFILE
, &rl
) == 0);
270 rl
.rlim_cur
= rl
.rlim_max
;
271 PAssertOS(setrlimit(RLIMIT_NOFILE
, &rl
) == 0);
275 // initialise the housekeeping thread
276 housekeepingThread
= NULL
;
282 PProcess::~PProcess()
284 // Don't wait for housekeeper to stop if Terminate() is called from it.
285 if (housekeepingThread
!= NULL
&& PThread::Current() != housekeepingThread
) {
286 housekeepingThread
->SetClosing();
288 housekeepingThread
->WaitForTermination();
289 delete housekeepingThread
;
290 housekeepingThread
= 0;
292 // XXX try to gracefully handle shutdown transient where the housekeeping
293 // XXX thread hasn't managed to clean up all the threads
294 while (PollNotificationQueue(kDurationImmediate
)) ;
302 // see InitialiseProcessThread()
306 void PThread::InitialiseProcessThread()
309 PX_origStackSize
= 0;
311 PX_threadId
= MPCurrentTaskID();
316 // Sadly, Mac OS MPThreads can't just initialize a block of memory into
317 // an MPSemaphore (XXX ought to be a CriticalRegion, but they're broken
318 // in Mac OS X 10.0.x!)
320 if ((err
= MPCreateSemaphore(1,1,&PX_suspendMutex
))
323 throw std::bad_alloc();
326 ((PProcess
*)this)->activeThreads
.DisallowDeleteObjects();
327 ((PProcess
*)this)->activeThreads
.SetAt((unsigned)PX_threadId
, this);
331 PThread::PThread(PINDEX stackSize
,
332 AutoDeleteFlag deletion
,
333 Priority
/*priorityLevel*/,
334 const PString
& name
)
335 : threadName(name
), PX_signature(kMPThreadSig
)
339 PAssert(stackSize
> 0, PInvalidParameter
);
341 PX_origStackSize
= stackSize
;
342 autoDelete
= (deletion
== AutoDeleteThread
);
344 // Sadly, Mac OS MPThreads can't just initialize a block of memory into
345 // an MPSemaphore (XXX ought to be a CriticalRegion, but they're broken
346 // in Mac OS X 10.0.x!)
348 if ((err
= MPCreateSemaphore(1,1,&PX_suspendMutex
)) != 0) {
349 PAssert(err
== 0, "MPCreateSemaphore failed");
350 throw std::bad_alloc();
355 // throw the new thread
365 ::close(unblockPipe
[0]);
366 ::close(unblockPipe
[1]);
369 MPDeleteSemaphore(PX_suspendMutex
);
372 fprintf(stderr
,"thread %p destructing\n", this);
374 PX_signature
= kMPDeadSig
;
377 void PThread::PX_NewThread(BOOL startSuspended
)
380 // initialise suspend counter and create mutex
381 PX_suspendCount
= startSuspended
? 1 : 0;
383 // initialise Suspend/Resume semaphore (for Mac OS X)
384 // XXX The MPThread manager allows for starting tasks "suspended", but I
385 // XXX suspect that only works if you have a debugger registered.
386 suspend_semaphore
= new PSemaphore(0,1);
395 fprintf(stderr
,"thread %p being started\n", (void *)this);
397 err
= MPCreateTask( (TaskProc
)PX_ThreadStart
, (void*)this,
399 terminationNotificationQueue
,
400 (void *)1, // param 1 == "death"
401 (void *)this, // param 2 == "PThread to clean up"
404 PAssert(err
== 0, "MPCreateTask failed");
405 if (err
) throw std::bad_alloc();
409 long PThread::PX_ThreadStart(void * arg
)
411 MPTaskID threadId
= MPCurrentTaskID();
413 // self-detach (no need)
415 PThread
* thread
= (PThread
*)arg
;
416 thread
->SetThreadName(thread
->GetThreadName());
418 PProcess
& process
= PProcess::Current();
420 // add thread to thread list
421 process
.threadMutex
.Wait();
422 process
.activeThreads
.SetAt((unsigned)threadId
, thread
);
423 process
.threadMutex
.Signal();
425 // if we are not supposed to start suspended, then don't wait
426 // if we are supposed to start suspended, then wait for a resume
428 if (thread
->PX_suspendCount
!= 0) {
429 thread
->suspend_semaphore
->Wait(); // Wait for the Resume
432 // now call the the thread main routine
433 //PTRACE(1, "tlibthrd\tAbout to call Main");
438 fprintf(stderr
,"thread %p returning\n", thread
);
444 void PProcess::SignalTimerChange()
446 if (housekeepingThread
== NULL
) {
448 BOOL oldIgnoreAllocations
= PMemoryHeap::SetIgnoreAllocations(TRUE
);
450 housekeepingThread
= new PHouseKeepingThread
;
452 PMemoryHeap::SetIgnoreAllocations(oldIgnoreAllocations
);
457 MPNotifyQueue(terminationNotificationQueue
, 0, 0, 0);
461 void PThread::PX_ThreadEnd(void * arg
)
463 PThread
* thread
= (PThread
*)arg
;
464 PProcess
& process
= PProcess::Current();
466 MPTaskID id
= thread
->PX_GetThreadId();
469 // remove this thread from the active thread list
470 process
.threadMutex
.Wait();
471 process
.activeThreads
.SetAt((unsigned)id
, NULL
);
472 process
.threadMutex
.Signal();
475 // delete the thread if required, note this is done this way to avoid
476 // a race condition, the thread ID cannot be zeroed before the if!
477 if (thread
->autoDelete
) {
478 thread
->PX_threadId
= 0; // Prevent terminating terminated thread
482 thread
->PX_threadId
= 0;
486 MPTaskID
PThread::PX_GetThreadId() const
492 void PThread::Restart()
501 void PThread::Terminate()
503 if (PX_origStackSize
<= 0)
509 PTRACE(1, "tlibthrd\tForcing termination of thread " << (void *)this);
511 if (Current() == this)
514 MPTaskID taskId
= PX_threadId
;
515 WaitForTermination();
516 // XXX Dire Consequences[TM] are warned of when one uses MPTerminateTask.
517 // XXX However, the same Dire Consequences are predicted (I think) for
518 // XXX pthread_kill which the PWLIB code already uses.
519 // XXX However, the only thing the cleanup function does is removes the
520 // XXX thread from the thread table, which is already performed by the
521 // XXX housekeeping thread; PWLIB doesn't try to salvage locks or
522 // XXX anything clever like that.
523 // XXX I just hope taskIds aren't quickly reused.
525 (void)MPTerminateTask(taskId
, kMPTaskAbortedErr
);
530 void PThread::PXSetWaitingSemaphore(PSemaphore
* sem
)
536 BOOL
PThread::IsTerminated() const
538 if (PX_threadId
== 0) {
539 //PTRACE(1, "tlibthrd\tIsTerminated(" << (void *)this << ") = 0");
543 #ifdef _not_def_ // Sigh. no MPGetNextTaskID on MOSX
544 // This seems like a silly way to do this, but I think it might work.
545 // The end condition for MPGetNextTaskID isn't documented, so I try both
546 // logical possibilities.
547 MPTaskID sometask
= 0;
548 MPProcessID myproc
= 0;
549 while (MPGetNextTaskID(myproc
, &sometask
) == noErr
) {
550 if (sometask
== 0) break;
551 if (sometask
== PX_threadId
) {
552 //PTRACE(1, "tlibthrd\tIsTerminated(" << (void *)this << ") not dead yet");
556 // didn't find it, it's dead
557 //PTRACE(1, "tlibthrd\tIsTerminated(" << (void *)this << ") = 0");
560 return FALSE
; // ENOCLUE
564 // Mac OS X and Darwin 1.2 does not support pthread_kill() or sigwait()
565 // so we cannot implement suspend and resume using signals. Instead we have a
566 // partial implementation using a Semaphore.
567 // As a result, we can create a thread in a suspended state and then 'resume'
568 // it, but once it is going, we can no longer suspend it.
569 // So, for Mac OS X, we will accept Resume() calls (or Suspend(FALSE))
570 // but reject Suspend(TRUE) calls with an Assertion. This will indicate
571 // to a user that we cannot Suspend threads on Mac OS X
573 void PThread::Suspend(BOOL susp
)
576 err
= MPWaitOnSemaphore(PX_suspendMutex
,kDurationForever
);
577 PAssert(err
== 0, "MPWaitOnSemaphore failed");
580 // Suspend - warn the user with an Assertion
581 PAssertAlways("Cannot suspend threads on Mac OS X due to lack of pthread_kill()");
584 // if resuming, then see if to really resume
585 else if (PX_suspendCount
> 0) {
587 if (PX_suspendCount
== 0) {
588 suspend_semaphore
->Signal();
592 err
= MPSignalSemaphore(PX_suspendMutex
);
593 PAssert( err
== 0, "MPSignalSemaphore failed");
596 void PThread::Resume()
602 BOOL
PThread::IsSuspended() const
609 err
= MPWaitOnSemaphore(PX_suspendMutex
, kDurationForever
);
610 PAssert(err
== 0, "MPWaitOnSemaphore failed");
611 BOOL suspended
= PX_suspendCount
> 0;
612 err
= MPSignalSemaphore(PX_suspendMutex
);
613 PAssert(err
== 0, "MPSignalSemaphore failed");
618 void PThread::SetAutoDelete(AutoDeleteFlag deletion
)
620 PAssert(deletion
!= AutoDeleteThread
|| this != &PProcess::Current(), PLogicError
);
621 autoDelete
= deletion
== AutoDeleteThread
;
625 void PThread::SetPriority(Priority
/*priorityLevel*/)
630 PThread::Priority
PThread::GetPriority() const
632 return LowestPriority
;
636 void PThread::Yield()
642 PThread
* PThread::Current()
644 PProcess
& process
= PProcess::Current();
645 process
.threadMutex
.Wait();
646 PThread
* thread
= process
.activeThreads
.GetAt((unsigned)MPCurrentTaskID());
647 process
.threadMutex
.Signal();
648 return PAssertNULL(thread
);
652 void PThread::Sleep(const PTimeInterval
& timeout
)
655 Duration delta
= kDurationForever
;
657 if (timeout
!= PMaxTimeInterval
) {
658 delta
= timeout
.GetMilliSeconds();
660 expiry
= AddDurationToAbsolute(delta
, UpTime());
662 (void)MPDelayUntil(&expiry
);
666 void PThread::WaitForTermination() const
668 PAssert(Current() != this, "Waiting for self termination!");
672 while (!IsTerminated()) {
673 PAssert(PX_signature
== kMPThreadSig
, "bad signature in living thread");
674 Current()->Sleep(10);
677 fprintf(stderr
,"spinning for termination of thread %p\n", (void *)this);
679 if (noHousekeeper
) PollNotificationQueue(kDurationImmediate
);
684 BOOL
PThread::WaitForTermination(const PTimeInterval
& maxWait
) const
686 PAssert(Current() != this, "Waiting for self termination!");
688 //PTRACE(1, "tlibthrd\tWaitForTermination(delay)");
691 PTimer timeout
= maxWait
;
692 while (!IsTerminated()) {
695 Current()->Sleep(10);
701 ///////////////////////////////////////////////////////////////////////////////
703 PSemaphore::PSemaphore(unsigned initial
, unsigned maxCount
)
705 OSStatus err
= MPCreateSemaphore(maxCount
, initial
, &semId
);
706 PAssert(err
== 0, "MPCreateSemaphore failed");
707 PAssert((long)semId
!= 0 && (long)semId
!= -1, "stupid semId");
711 PSemaphore::~PSemaphore()
713 OSStatus err
= MPDeleteSemaphore(semId
);
714 PAssert(err
== 0, "MPDeleteSemaphore failed");
715 *(long *)&semId
= -1;
719 void PSemaphore::Wait()
721 assert((long)semId
!= 0);
722 assert((long)semId
!= -1);
724 PAssert((long)semId
!= -1, "wait on destructed PSemaphore");
725 PAssert((long)semId
!= 0, "semId stomped");
726 OSStatus err
= MPWaitOnSemaphore(semId
, kDurationForever
);
727 PAssert(err
== 0, "MPWaitOnSemaphore failed");
731 BOOL
PSemaphore::Wait(const PTimeInterval
& waitTime
)
735 if (waitTime
== PMaxTimeInterval
) {
740 Duration timeout
= waitTime
.GetMilliSeconds();
741 if ((err
= MPWaitOnSemaphore(semId
, timeout
)) == noErr
)
743 if (err
== kMPTimeoutErr
)
745 PAssert(err
== 0, psprintf("timed wait error = %i", err
));
749 void PSemaphore::Signal()
751 OSStatus err
= MPSignalSemaphore(semId
);
752 // was it already signalled?
753 if (err
== kMPInsufficientResourcesErr
) err
= 0;
754 PAssert(err
== 0, "MPSignalSemaphore failed");
758 BOOL
PSemaphore::WillBlock() const
760 OSStatus err
= MPWaitOnSemaphore(semId
, kDurationImmediate
);
761 if (err
== kMPTimeoutErr
)
763 PAssert(err
== 0, psprintf("timed wait error = %i", err
));
764 (void)MPSignalSemaphore(semId
);
768 // Ideally, a PMutex would contain an MPCriticalSection instead of a
769 // semaphore, but the class derivation is outside the machine-specific
770 // code, and I'm unwilling to do something gross like implement a bogus
771 // constructor for PSemaphore which doesn't allocate a semaphore.
783 BOOL
PMutex::Wait(const PTimeInterval
& timeout
)
785 return PSemaphore::Wait(timeout
);
788 void PMutex::Signal()
790 PSemaphore::Signal();
793 BOOL
PMutex::WillBlock() const
795 return PSemaphore::WillBlock();
798 PSyncPoint::PSyncPoint()