//C- -*- C++ -*- //C- ------------------------------------------------------------------- //C- DjVuLibre-3.5 //C- Copyright (c) 2002 Leon Bottou and Yann Le Cun. //C- Copyright (c) 2001 AT&T //C- //C- This software is subject to, and may be distributed under, the //C- GNU General Public License, Version 2. The license should have //C- accompanied the software or you may obtain a copy of the license //C- from the Free Software Foundation at http://www.fsf.org . //C- //C- This program is distributed in the hope that it will be useful, //C- but WITHOUT ANY WARRANTY; without even the implied warranty of //C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //C- GNU General Public License for more details. //C- //C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library //C- distributed by Lizardtech Software. On July 19th 2002, Lizardtech //C- Software authorized us to replace the original DjVu(r) Reference //C- Library notice by the following text (see doc/lizard2002.djvu): //C- //C- ------------------------------------------------------------------ //C- | DjVu (r) Reference Library (v. 3.5) //C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved. //C- | The DjVu Reference Library is protected by U.S. Pat. No. //C- | 6,058,214 and patents pending. //C- | //C- | This software is subject to, and may be distributed under, the //C- | GNU General Public License, Version 2. The license should have //C- | accompanied the software or you may obtain a copy of the license //C- | from the Free Software Foundation at http://www.fsf.org . //C- | //C- | The computer code originally released by LizardTech under this //C- | license and unmodified by other parties is deemed "the LIZARDTECH //C- | ORIGINAL CODE." Subject to any third party intellectual property //C- | claims, LizardTech grants recipient a worldwide, royalty-free, //C- | non-exclusive license to make, use, sell, or otherwise dispose of //C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the //C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU //C- | General Public License. This grant only confers the right to //C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to //C- | the extent such infringement is reasonably necessary to enable //C- | recipient to make, have made, practice, sell, or otherwise dispose //C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to //C- | any greater extent that may be necessary to utilize further //C- | modifications or combinations. //C- | //C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY //C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED //C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF //C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. //C- +------------------------------------------------------------------ // // $Id: GThreads.cpp,v 1.15 2004/04/21 14:54:43 leonb Exp $ // $Name: release_3_5_15 $ #ifdef HAVE_CONFIG_H # include "config.h" #endif #if NEED_GNUG_PRAGMAS # pragma implementation #endif // This file defines machine independent classes // for running and synchronizing threads. // - Author: Leon Bottou, 01/1998 // From: Leon Bottou, 1/31/2002 // Almost unchanged by Lizardtech. // GSafeFlags should go because it not as safe as it claims. #include "GThreads.h" #include "GException.h" #include "DjVuMessageLite.h" #include #include // ---------------------------------------- // Consistency check #if THREADMODEL!=NOTHREADS #ifdef USE_EXCEPTION_EMULATION #warning "Compiler must support thread safe exceptions" #endif //USE_EXCEPTION_EMULATION #if defined(__GNUC__) #if (__GNUC__<2) || ((__GNUC__==2) && (__GNUC_MINOR__<=8)) #warning "GCC 2.8 exceptions are not thread safe." #warning "Use properly configured EGCS-1.1 or greater." #endif // (__GNUC__<2 ... #endif // defined(__GNUC__) #endif // THREADMODEL!=NOTHREADS #ifndef _DEBUG #if defined(DEBUG) #define _DEBUG /* */ #elif DEBUGLVL >= 1 #define _DEBUG /* */ #endif #endif #if THREADMODEL==WINTHREADS # include #endif #if THREADMODEL==COTHREADS # include # include # include # include # include #endif #ifdef HAVE_NAMESPACES namespace DJVU { # ifdef NOT_DEFINED // Just to fool emacs c++ mode } #endif #endif // ---------------------------------------- // NOTHREADS // ---------------------------------------- #if THREADMODEL==NOTHREADS int GThread::create( void (*entry)(void*), void *arg) { (*entry)(arg); return 0; } #endif // ---------------------------------------- // WIN32 IMPLEMENTATION // ---------------------------------------- #if THREADMODEL==WINTHREADS static unsigned __stdcall start(void *arg) { GThread *gt = (GThread*)arg; try { G_TRY { gt->xentry( gt->xarg ); } G_CATCH(ex) { ex.perror(); DjVuMessageLite::perror( ERR_MSG("GThreads.uncaught") ); #ifdef _DEBUG abort(); #endif } G_ENDCATCH; } catch(...) { DjVuMessageLite::perror( ERR_MSG("GThreads.unrecognized") ); #ifdef _DEBUG abort(); #endif } return 0; } GThread::GThread(int stacksize) : hthr(0), thrid(0), xentry(0), xarg(0) { } GThread::~GThread() { if (hthr) CloseHandle(hthr); hthr = 0; thrid = 0; } int GThread::create(void (*entry)(void*), void *arg) { if (hthr) return -1; xentry = entry; xarg = arg; unsigned uthread = 0; hthr = (HANDLE)_beginthreadex(NULL, 0, start, (void*)this, 0, &uthread); thrid = (DWORD) uthread; if (hthr) return 0; return -1; } void GThread::terminate() { OutputDebugString("Terminating thread.\n"); if (hthr) TerminateThread(hthr,0); } int GThread::yield() { Sleep(0); return 0; } void * GThread::current() { return (void*) GetCurrentThreadId(); } struct thr_waiting { struct thr_waiting *next; struct thr_waiting *prev; BOOL waiting; HANDLE gwait; }; GMonitor::GMonitor() : ok(0), count(1), head(0), tail(0) { InitializeCriticalSection(&cs); locker = GetCurrentThreadId(); ok = 1; } GMonitor::~GMonitor() { ok = 0; EnterCriticalSection(&cs); for (struct thr_waiting *w=head; w; w=w->next) SetEvent(w->gwait); LeaveCriticalSection(&cs); DeleteCriticalSection(&cs); } void GMonitor::enter() { DWORD self = GetCurrentThreadId(); if (count>0 || self!=locker) { if (ok) EnterCriticalSection(&cs); locker = self; count = 1; } count -= 1; } void GMonitor::leave() { DWORD self = GetCurrentThreadId(); if (ok && (count>0 || self!=locker)) G_THROW( ERR_MSG("GThreads.not_acq_broad") ); count += 1; if (count > 0) { count = 1; if (ok) LeaveCriticalSection(&cs); } } void GMonitor::signal() { if (ok) { DWORD self = GetCurrentThreadId(); if (count>0 || self!=locker) G_THROW( ERR_MSG("GThreads.not_acq_signal") ); for (struct thr_waiting *w=head; w; w=w->next) if (w->waiting) { SetEvent(w->gwait); w->waiting = FALSE; break; // Only one thread is allowed to run! } } } void GMonitor::broadcast() { if (ok) { DWORD self = GetCurrentThreadId(); if (count>0 || self!=locker) G_THROW( ERR_MSG("GThreads.not_acq_broad") ); for (struct thr_waiting *w=head; w; w=w->next) if (w->waiting) { SetEvent(w->gwait); w->waiting = FALSE; } } } void GMonitor::wait() { // Check state DWORD self = GetCurrentThreadId(); if (count>0 || self!=locker) G_THROW( ERR_MSG("GThreads.not_acq_wait") ); // Wait if (ok) { // Prepare wait record struct thr_waiting waitrec; waitrec.waiting = TRUE; waitrec.gwait = CreateEvent(NULL,FALSE,FALSE,NULL); waitrec.next = 0; waitrec.prev = tail; // Link wait record (protected by critical section) *(waitrec.next ? &waitrec.next->prev : &tail) = &waitrec; *(waitrec.prev ? &waitrec.prev->next : &head) = &waitrec; // Start wait int sav_count = count; count = 1; LeaveCriticalSection(&cs); WaitForSingleObject(waitrec.gwait,INFINITE); // Re-acquire EnterCriticalSection(&cs); count = sav_count; locker = self; // Unlink wait record *(waitrec.next ? &waitrec.next->prev : &tail) = waitrec.prev; *(waitrec.prev ? &waitrec.prev->next : &head) = waitrec.next; CloseHandle(waitrec.gwait); } } void GMonitor::wait(unsigned long timeout) { // Check state DWORD self = GetCurrentThreadId(); if (count>0 || self!=locker) G_THROW( ERR_MSG("GThreads.not_acq_wait") ); // Wait if (ok) { // Prepare wait record struct thr_waiting waitrec; waitrec.waiting = TRUE; waitrec.gwait = CreateEvent(NULL,FALSE,FALSE,NULL); waitrec.next = 0; waitrec.prev = tail; // Link wait record (protected by critical section) *(waitrec.prev ? &waitrec.prev->next : &head) = &waitrec; *(waitrec.next ? &waitrec.next->prev : &tail) = &waitrec; // Start wait int sav_count = count; count = 1; LeaveCriticalSection(&cs); WaitForSingleObject(waitrec.gwait,timeout); // Re-acquire EnterCriticalSection(&cs); count = sav_count; locker = self; // Unlink wait record *(waitrec.next ? &waitrec.next->prev : &tail) = waitrec.prev; *(waitrec.prev ? &waitrec.prev->next : &head) = waitrec.next; CloseHandle(waitrec.gwait); } } #endif // ---------------------------------------- // MACTHREADS IMPLEMENTATION (from Praveen) // ---------------------------------------- #if THREADMODEL==MACTHREADS // Doubly linked list of waiting threads struct thr_waiting { struct thr_waiting *next; // ptr to next waiting thread record struct thr_waiting *prev; // ptr to ptr to this waiting thread unsigned long thid; // id of waiting thread int *wchan; // cause of the wait }; static struct thr_waiting *first_waiting_thr = 0; static struct thr_waiting *last_waiting_thr = 0; // Stops current thread. // Argument ``self'' must be current thread id. // Assumes ``ThreadBeginCritical'' has been called before. static void macthread_wait(ThreadID self, int *wchan) { // Prepare and link wait record struct thr_waiting wait; // no need to malloc :-) wait.thid = self; wait.wchan = wchan; wait.next = 0; wait.prev = last_waiting_thr; *(wait.prev ? &wait.prev->next : &first_waiting_thr ) = &wait; *(wait.next ? &wait.next->prev : &last_waiting_thr ) = &wait; // Leave critical section and start waiting. (*wchan)++; SetThreadStateEndCritical(self, kStoppedThreadState, kNoThreadID); // The Apple documentation says that the above call reschedules a new // thread. Therefore it will only return when the thread wakes up. ThreadBeginCritical(); (*wchan)--; // Unlink wait record *(wait.prev ? &wait.prev->next : &first_waiting_thr ) = wait.next; *(wait.next ? &wait.next->prev : &last_waiting_thr ) = wait.prev; // Returns from the wait. } // Wakeup one thread or all threads waiting on cause wchan static void macthread_wakeup(int *wchan, int onlyone) { if (*wchan == 0) return; for (struct thr_waiting *q=first_waiting_thr; q; q=q->next) if (q->wchan == wchan) { // Found a waiting thread q->wchan = 0; SetThreadState(q->thid, kReadyThreadState, kNoThreadID); if (onlyone) return; } } GThread::GThread(int stacksize) : thid(kNoThreadID), xentry(0), xarg(0) { } GThread::~GThread(void) { thid = kNoThreadID; } pascal void * GThread::start(void *arg) { GThread *gt = (GThread*)arg; try { G_TRY { (gt->xentry)(gt->xarg); } G_CATCH(ex) { ex.perror(); DjVuMessageLite::perror( ERR_MSG("GThreads.uncaught") ); #ifdef _DEBUG abort(); #endif } G_ENDCATCH; } catch(...) { DjVuMessageLite::perror( ERR_MSG("GThreads.unrecognized") ); #ifdef _DEBUG abort(); #endif } return 0; } int GThread::create(void (*entry)(void*), void *arg) { if (xentry || thid!=kNoThreadID) return -1; xentry = entry; xarg = arg; int err = NewThread( kCooperativeThread, GThread::start , this, 0, kCreateIfNeeded, (void**)nil, &thid ); if( err != noErr ) return err; return 0; } void GThread::terminate() { if (thid != kNoThreadID) { DisposeThread( thid, NULL, false ); thid = kNoThreadID; } } int GThread::yield() { YieldToAnyThread(); return 0; } void* GThread::current() { unsigned long thid = kNoThreadID; GetCurrentThread(&thid); return (void*) thid; } // GMonitor implementation GMonitor::GMonitor() : ok(0), count(1), locker(0), wlock(0), wsig(0) { locker = kNoThreadID; ok = 1; } GMonitor::~GMonitor() { ok = 0; ThreadBeginCritical(); macthread_wakeup(&wsig, 0); macthread_wakeup(&wlock, 0); ThreadEndCritical(); YieldToAnyThread(); } void GMonitor::enter() { ThreadID self; GetCurrentThread(&self); ThreadBeginCritical(); if (count>0 || self!=locker) { while (ok && count<=0) macthread_wait(self, &wlock); count = 1; locker = self; } count -= 1; ThreadEndCritical(); } void GMonitor::leave() { ThreadID self; GetCurrentThread(&self); if (ok && (count>0 || self!=locker)) G_THROW( ERR_MSG("GThreads.not_acq_leave") ); ThreadBeginCritical(); if (++count > 0) macthread_wakeup(&wlock, 1); ThreadEndCritical(); } void GMonitor::signal() { ThreadID self; GetCurrentThread(&self); if (count>0 || self!=locker) G_THROW( ERR_MSG("GThreads.not_acq_signal") ); ThreadBeginCritical(); macthread_wakeup(&wsig, 1); ThreadEndCritical(); } void GMonitor::broadcast() { ThreadID self; GetCurrentThread(&self); if (count>0 || self!=locker) G_THROW( ERR_MSG("GThreads.not_acq_broad") ); ThreadBeginCritical(); macthread_wakeup(&wsig, 0); ThreadEndCritical(); } void GMonitor::wait() { // Check state ThreadID self; GetCurrentThread(&self); if (count>0 || locker!=self) G_THROW( ERR_MSG("GThreads.not_acq_wait") ); // Wait if (ok) { // Atomically release monitor and wait ThreadBeginCritical(); int sav_count = count; count = 1; macthread_wakeup(&wlock, 1); macthread_wait(self, &wsig); // Re-acquire while (ok && count<=0) macthread_wait(self, &wlock); count = sav_count; locker = self; ThreadEndCritical(); } } void GMonitor::wait(unsigned long timeout) { // Timeouts are not used for anything important. // Just ignore the timeout and wait the regular way. wait(); } #endif // ---------------------------------------- // POSIXTHREADS IMPLEMENTATION // ---------------------------------------- #if THREADMODEL==POSIXTHREADS #if defined(CMA_INCLUDE) #define DCETHREADS #define pthread_key_create pthread_keycreate #else #define pthread_mutexattr_default NULL #define pthread_condattr_default NULL #endif void * GThread::start(void *arg) { GThread *gt = (GThread*)arg; #ifdef DCETHREADS #ifdef CANCEL_ON pthread_setcancel(CANCEL_ON); pthread_setasynccancel(CANCEL_ON); #endif #else // !DCETHREADS #ifdef PTHREAD_CANCEL_ENABLE pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0); #endif #ifdef PTHREAD_CANCEL_ASYNCHRONOUS pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0); #endif #endif // Catch exceptions #ifdef __EXCEPTIONS try { #endif G_TRY { (gt->xentry)(gt->xarg); } G_CATCH(ex) { ex.perror(); DjVuMessageLite::perror( ERR_MSG("GThreads.uncaught") ); #ifdef _DEBUG abort(); #endif } G_ENDCATCH; #ifdef __EXCEPTIONS } catch(...) { DjVuMessageLite::perror( ERR_MSG("GThreads.unrecognized") ); #ifdef _DEBUG abort(); #endif } #endif return 0; } // GThread GThread::GThread(int stacksize) : hthr(0), xentry(0), xarg(0) { } GThread::~GThread() { hthr = 0; } int GThread::create(void (*entry)(void*), void *arg) { if (xentry || xarg) return -1; xentry = entry; xarg = arg; #ifdef DCETHREADS int ret = pthread_create(&hthr, pthread_attr_default, GThread::start, (void*)this); if (ret >= 0) pthread_detach(hthr); #else pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); int ret = pthread_create(&hthr, &attr, start, (void*)this); pthread_attr_destroy(&attr); #endif return ret; } void GThread::terminate() { if (xentry || xarg) pthread_cancel(hthr); } int GThread::yield() { #ifdef DCETHREADS pthread_yield(); #else // should use sched_yield() when available. static struct timeval timeout = { 0, 0 }; ::select(0, 0,0,0, &timeout); #endif return 0; } void* GThread::current() { pthread_t self = pthread_self(); #if defined(pthread_getunique_np) return (void*) pthread_getunique_np( & self ); #elif defined(cma_thread_get_unique) return (void*) cma_thread_get_unique( & self ); #else return (void*) self; #endif } // -- GMonitor GMonitor::GMonitor() : ok(0), count(1), locker(0) { // none of this should be necessary ... in theory. #ifdef PTHREAD_MUTEX_INITIALIZER static pthread_mutex_t tmutex=PTHREAD_MUTEX_INITIALIZER; memcpy(&mutex,&tmutex,sizeof(mutex)); #endif #ifdef PTHREAD_COND_INITIALIZER static pthread_cond_t tcond=PTHREAD_COND_INITIALIZER; memcpy(&cond,&tcond,sizeof(cond)); #endif // standard pthread_mutex_init(&mutex, pthread_mutexattr_default); pthread_cond_init(&cond, pthread_condattr_default); locker = pthread_self(); ok = 1; } GMonitor::~GMonitor() { ok = 0; pthread_cond_destroy(&cond); pthread_mutex_destroy(&mutex); } void GMonitor::enter() { pthread_t self = pthread_self(); if (count>0 || !pthread_equal(locker, self)) { if (ok) pthread_mutex_lock(&mutex); locker = self; count = 1; } count -= 1; } void GMonitor::leave() { pthread_t self = pthread_self(); if (ok && (count>0 || !pthread_equal(locker, self))) G_THROW( ERR_MSG("GThreads.not_acq_broad") ); count += 1; if (count > 0) { count = 1; if (ok) pthread_mutex_unlock(&mutex); } } void GMonitor::signal() { if (ok) { pthread_t self = pthread_self(); if (count>0 || !pthread_equal(locker, self)) G_THROW( ERR_MSG("GThreads.not_acq_signal") ); pthread_cond_signal(&cond); } } void GMonitor::broadcast() { if (ok) { pthread_t self = pthread_self(); if (count>0 || !pthread_equal(locker, self)) G_THROW( ERR_MSG("GThreads.not_acq_broad") ); pthread_cond_broadcast(&cond); } } void GMonitor::wait() { // Check pthread_t self = pthread_self(); if (count>0 || !pthread_equal(locker, self)) G_THROW( ERR_MSG("GThreads.not_acq_wait") ); // Wait if (ok) { // Release int sav_count = count; count = 1; // Wait pthread_cond_wait(&cond, &mutex); // Re-acquire count = sav_count; locker = self; } } void GMonitor::wait(unsigned long timeout) { // Check pthread_t self = pthread_self(); if (count>0 || !pthread_equal(locker, self)) G_THROW( ERR_MSG("GThreads.not_acq_wait") ); // Wait if (ok) { // Release int sav_count = count; count = 1; // Wait struct timeval abstv; struct timespec absts; gettimeofday(&abstv, NULL); // grrr absts.tv_sec = abstv.tv_sec + timeout/1000; absts.tv_nsec = abstv.tv_usec*1000 + (timeout%1000)*1000000; if (absts.tv_nsec > 1000000000) { absts.tv_nsec -= 1000000000; absts.tv_sec += 1; } pthread_cond_timedwait(&cond, &mutex, &absts); // Re-acquire count = sav_count; locker = self; } } #endif // ---------------------------------------- // CUSTOM COOPERATIVE THREADS // ---------------------------------------- #if THREADMODEL==COTHREADS #ifndef __GNUG__ #error "COTHREADS require G++" #endif #if (__GNUC__<2) || ((__GNUC__==2) && (__GNUC_MINOR__<=90)) #warning "COTHREADS require EGCS-1.1.1 with Leon's libgcc patch." #warning "You may have trouble with thread-unsafe exceptions..." #define NO_LIBGCC_HOOKS #endif // -------------------------------------- constants // Minimal stack size #define MINSTACK (32*1024) // Default stack size #define DEFSTACK (127*1024) // Maxtime between checking fdesc (ms) #define MAXFDWAIT (200) // Maximum time to wait in any case #define MAXWAIT (60*60*1000) // Maximum penalty for hog task (ms) #define MAXPENALTY (1000) // Trace task switches #undef COTHREAD_TRACE #undef COTHREAD_TRACE_VERBOSE // -------------------------------------- context switch code struct mach_state { jmp_buf buf; }; static void mach_switch(mach_state *st1, mach_state *st2) { #if #cpu(sparc) asm("ta 3"); // save register windows #endif if (! setjmp(st1->buf)) longjmp(st2->buf, 1); } static void mach_start(mach_state *st1, void *pc, char *stacklo, char *stackhi) { #if #cpu(sparc) asm("ta 3"); // save register windows #endif if (! setjmp(st1->buf)) { // The following code must perform two tasks: // -- set stack pointer to a proper value between #stacklo# and #stackhi#. // -- branch to or call the function at address #pc#. // This function never returns ... so there is no need to save anything #if #cpu(mips) char *sp = (char*)(((unsigned long)stackhi-16) & ~0xff); asm volatile ("move $sp,%0\n\t" // set new stack pointer "move $25,%1\n\t" // call subroutine via $25 "jal $25\n\t" // call subroutine via $25 "nop" // delay slot : : "r" (sp), "r" (pc) ); #elif #cpu(i386) char *sp = (char*)(((unsigned long)stackhi-16) & ~0xff); asm volatile ("movl %0,%%esp\n\t" // set new stack pointer "call *%1" // call function : : "r" (sp), "r" (pc) ); #elif #cpu(sparc) char *sp = (char*)(((unsigned long)stackhi-16) & ~0xff); asm volatile ("ta 3\n\t" // saving the register windows will not hurt. "mov %0,%%sp\n\t" // set new stack pointer "call %1,0\n\t" // call function "nop" // delay slot : : "r" (sp), "r" (pc) ); #elif #cpu(hppa) char *sp = (char*)(((unsigned long)stacklo+128+255) & ~0xff); asm volatile("copy %0,%%sp\n\t" // set stack pointer "copy %1,%%r22\n\t" // set call address ".CALL\n\t" // call pseudo instr (why?) "bl $$dyncall,%%r31\n\t" // call "copy %%r31,%%r2" // delay slot ??? : : "r" (sp), "r" (pc) ); #elif #cpu(alpha) char *sp = (char*)(((unsigned long)stackhi-16) & ~0xff); asm volatile ("bis $31,%0,$30\n\t" // set new stack pointer "bis $31,%1,$27\n\t" // load function pointer "jsr $26,($27),0" // call function : : "r" (sp), "r" (pc) ); #elif #cpu(powerpc) char *sp = (char*)(((unsigned long)stackhi-16) & ~0xff); asm volatile ("mr 1,%0\n\t" // set new stack pointer "mr 0,%1\n\t" // load func pointer into r0 "mtlr 0\n\t" // load link register with r0 "blrl" // branch : : "r" (sp), "r" (pc) ); #elif #cpu(m68k) && defined(COTHREAD_UNTESTED) char *sp = (char*)(((unsigned long)stackhi-16) & ~0xff); asm volatile ("move%.l %0,%Rsp\n\t" // set new stack pointer "jmp %a1" // branch to address %1 : : "r" (sp), "a" (pc) ); #elif #cpu(arm) && defined(COTHREAD_UNTESTED) char *sp = (char*)(((unsigned long)stackhi-16) & ~0xff); asm volatile ("mov%?\t%|sp, %0\n\t" // set new stack pointer "mov%?\t%|pc, %1" // branch to address %1 : : "r" (sp), "r" (pc) ); #else #error "COTHREADS not supported on this machine." #error "Try -DTHREADMODEL=NOTHREADS." #endif // We should never reach this point abort(); // Note that this call to abort() makes sure // that function mach_start() is compiled as a non-leaf // function. It is indeed a non-leaf function since the // piece of assembly code calls a function, but the compiler // would not know without the call to abort() ... } } #ifdef CHECK // This code can be used to verify that task switching works. char stack[16384]; mach_state st1, st2; void th2() { puts("2b"); mach_switch(&st2, &st1); puts("4b"); mach_switch(&st2, &st1); puts("6b"); mach_switch(&st2, &st1); } void th2relay() { th2(); puts("ooops\n"); } void th1() { mach_start(&st1, (void*)th2relay, stack, stack+sizeof(stack)); puts("3a"); mach_switch(&st1, &st2); puts("5a"); mach_switch(&st1, &st2); } int main() { puts("1a"); th1(); puts("6a"); } #endif // -------------------------------------- select struct coselect { int nfds; fd_set rset; fd_set wset; fd_set eset; }; static void coselect_merge(coselect *dest, coselect *from) { int i; int nfds = from->nfds; if (nfds > dest->nfds) dest->nfds = nfds; for (i=0; irset)) FD_SET(i, &dest->rset); for (i=0; iwset)) FD_SET(i, &dest->wset); for (i=0; ieset)) FD_SET(i, &dest->eset); } static int coselect_test(coselect *c) { static timeval tmzero = {0,0}; fd_set copyr = c->rset; fd_set copyw = c->wset; fd_set copye = c->eset; return select(c->nfds, ©r, ©w, ©e, &tmzero); } // -------------------------------------- cotask class GThread::cotask { public: #ifndef NO_LIBGCC_HOOKS cotask(const int xstacksize,void *); #else cotask(const int xstacksize); #endif ~cotask(); class GThread::cotask *next; class GThread::cotask *prev; // context mach_state regs; // stack information char *stack; GPBuffer gstack; int stacksize; // timing information unsigned long over; // waiting information void *wchan; coselect *wselect; unsigned long *maxwait; // delete after termination bool autodelete; // egcs exception support #ifndef NO_LIBGCC_HOOKS void *ehctx; #endif }; #ifndef NO_LIBGCC_HOOKS GThread::cotask::cotask(const int xstacksize, void *xehctx) #else GThread::cotask::cotask(const int xstacksize) #endif : next(0), prev(0), gstack(stack,xstacksize), stacksize(xstacksize), over(0), wchan(0), wselect(0), maxwait(0), autodelete(false) #ifndef NO_LIBGCC_HOOKS ,ehctx(xehctx) #endif { memset(®s,0,sizeof(regs)); } static GThread::cotask *maintask = 0; static GThread::cotask *curtask = 0; static GThread::cotask *autodeletetask = 0; static unsigned long globalmaxwait = 0; static void (*scheduling_callback)(int) = 0; static timeval time_base; GThread::cotask::~cotask() { gstack.resize(0); #ifndef NO_LIBGCC_HOOKS if (ehctx) free(ehctx); ehctx = 0; #endif } static void cotask_free(GThread::cotask *task) { #ifdef COTHREAD_TRACE DjVuPrintErrorUTF8("cothreads: freeing task %p with autodelete=%d\n", task,task->autodelete); #endif if (task!=maintask) { delete task; } } // -------------------------------------- time static unsigned long time_elapsed(int reset=1) { timeval tm; gettimeofday(&tm, NULL); long msec = (tm.tv_usec-time_base.tv_usec)/1000; unsigned long elapsed = (long)(tm.tv_sec-time_base.tv_sec)*1000 + msec; if (reset && elapsed>0) { #ifdef COTHREAD_TRACE #ifdef COTHREAD_TRACE_VERBOSE DjVuPrintErrorUTF8("cothreads: %4ld ms in task %p\n", elapsed, curtask); #endif #endif time_base.tv_sec = tm.tv_sec; time_base.tv_usec += msec*1000; } return elapsed; } // -------------------------------------- scheduler static int cotask_yield() { // ok if (! maintask) return 0; // get elapsed time and return immediately when it is too small unsigned long elapsed = time_elapsed(); if (elapsed==0 && curtask->wchan==0 && curtask->prev && curtask->next) return 0; // adjust task running time curtask->over += elapsed; if (curtask->over > MAXPENALTY) curtask->over = MAXPENALTY; // start scheduling reschedule: // try unblocking tasks GThread::cotask *n = curtask->next; GThread::cotask *q = n; do { if (q->wchan) { if (q->maxwait && *q->maxwait<=elapsed) { *q->maxwait = 0; q->wchan=0; q->maxwait=0; q->wselect=0; } else if (q->wselect && globalmaxwait<=elapsed && coselect_test(q->wselect)) { q->wchan=0; if (q->maxwait) *q->maxwait -= elapsed; q->maxwait = 0; q->wselect=0; } if (q->maxwait) *q->maxwait -= elapsed; } q = q->next; } while (q!=n); // adjust globalmaxwait if (globalmaxwait < elapsed) globalmaxwait = MAXFDWAIT; else globalmaxwait -= elapsed; // find best candidate static int count; unsigned long best = MAXPENALTY + 1; GThread::cotask *r = 0; count = 0; q = n; do { if (! q->wchan) { count += 1; if (best > q->over) { r = q; best = r->over; } } q = q->next; } while (q != n); // found if (count > 0) { // adjust over q = n; do { q->over = (q->over>best ? q->over-best : 0); q = q->next; } while (q != n); // Switch if (r != curtask) { #ifdef COTHREAD_TRACE DjVuPrintErrorUTF8("cothreads: ----- switch to %p [%ld]\n", r, best); #endif GThread::cotask *old = curtask; curtask = r; mach_switch(&old->regs, &curtask->regs); } // handle autodelete if (autodeletetask && autodeletetask->autodelete) cotask_free(autodeletetask); autodeletetask = 0; // return if (count == 1) return 1; return 0; } // No task ready count = 0; unsigned long minwait = MAXWAIT; coselect allfds; allfds.nfds = 1; FD_ZERO(&allfds.rset); FD_ZERO(&allfds.wset); FD_ZERO(&allfds.eset); q = n; do { if (q->maxwait || q->wselect) count += 1; if (q->maxwait && *q->maxwaitmaxwait; if (q->wselect) coselect_merge(&allfds, q->wselect); q = q->next; } while (q != n); // abort on deadlock if (count == 0) { DjVuMessageLite::perror( ERR_MSG("GThreads.panic") ); abort(); } // select timeval tm; tm.tv_sec = minwait/1000; tm.tv_usec = 1000*(minwait-1000*tm.tv_sec); select(allfds.nfds,&allfds.rset, &allfds.wset, &allfds.eset, &tm); // reschedule globalmaxwait = 0; elapsed = time_elapsed(); goto reschedule; } static void cotask_terminate(GThread::cotask *task) { #ifdef COTHREAD_TRACE DjVuPrintErrorUTF8("cothreads: terminating task %p\n", task); #endif if (task && task!=maintask) { if (task->prev && task->next) { if (scheduling_callback) (*scheduling_callback)(GThread::CallbackTerminate); task->prev->next = task->next; task->next->prev = task->prev; // mark task as terminated task->prev = 0; // self termination if (task == curtask) { if (task->autodelete) autodeletetask = task; cotask_yield(); } } } } static void cotask_wakeup(void *wchan, int onlyone) { if (maintask && curtask) { GThread::cotask *n = curtask->next; GThread::cotask *q = n; do { if (q->wchan == wchan) { q->wchan=0; q->maxwait=0; q->wselect=0; q->over = 0; if (onlyone) return; } q = q->next; } while (q!=n); } } // -------------------------------------- select / get_select static int cotask_select(int nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *tm) { // bypass if (maintask==0 || (tm && tm->tv_sec==0 && tm->tv_usec<1000)) return select(nfds, rfds, wfds, efds, tm); // copy parameters unsigned long maxwait = 0; coselect parm; // set waiting info curtask->wchan = (void*)&parm; if (rfds || wfds || efds) { parm.nfds = nfds; if (rfds) { parm.rset=*rfds; } else { FD_ZERO(&parm.rset); } if (wfds) { parm.wset=*wfds; } else { FD_ZERO(&parm.wset); } if (efds) { parm.eset=*efds; } else { FD_ZERO(&parm.eset); } curtask->wselect = &parm; } if (tm) { maxwait = time_elapsed(0) + tm->tv_sec*1000 + tm->tv_usec/1000; curtask->maxwait = &maxwait; } // reschedule cotask_yield(); // call select to update masks if (tm) { tm->tv_sec = maxwait/1000; tm->tv_usec = 1000*(maxwait-1000*tm->tv_sec); } static timeval tmzero = {0,0}; return select(nfds, rfds, wfds, efds, &tmzero); } static void cotask_get_select(int &nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, unsigned long &timeout) { int ready = 1; unsigned long minwait = MAXWAIT; unsigned long elapsed = time_elapsed(0); coselect allfds; allfds.nfds=0; FD_ZERO(&allfds.rset); FD_ZERO(&allfds.wset); FD_ZERO(&allfds.eset); if (curtask) { GThread::cotask *q=curtask->next; while (q != curtask) { ready++; if (q->wchan) { if (q->wselect) coselect_merge(&allfds, q->wselect); if (q->maxwait && *q->maxwaitmaxwait; ready--; } q = q->next; } } timeout = 0; nfds=allfds.nfds; *rfds=allfds.rset; *wfds=allfds.wset; *efds=allfds.eset; if (ready==1 && minwait>elapsed) timeout = minwait-elapsed; } // -------------------------------------- libgcc hook #ifndef NO_LIBGCC_HOOKS // These are exported by Leon's patched version of libgcc.a // Let's hope that the egcs people will include the patch in // the distributions. extern "C" { extern void* (*__get_eh_context_ptr)(void); extern void* __new_eh_context(void); } // This function is called via the pointer __get_eh_context_ptr // by the internal mechanisms of egcs. It must return the // per-thread event handler context. This is necessary to // implement thread safe exceptions on some machine and/or // when flag -fsjlj-exception is set. static void * cotask_get_eh_context() { if (curtask) return curtask->ehctx; else if (maintask) return maintask->ehctx; DjVuMessageLite::perror( ERR_MSG("GThreads.co_panic") ); abort(); } #endif // -------------------------------------- GThread void GThread::set_scheduling_callback(void (*call)(int)) { if (scheduling_callback) G_THROW( ERR_MSG("GThreads.dupl_callback") ); scheduling_callback = call; } GThread::GThread(int stacksize) : task(0), xentry(0), xarg(0) { // check argument if (stacksize < 0) stacksize = DEFSTACK; if (stacksize < MINSTACK) stacksize = MINSTACK; // initialization if (! maintask) { #ifndef NO_LIBGCC_HOOKS static GThread::cotask comaintask(0,(*__get_eh_context_ptr)()); __get_eh_context_ptr = cotask_get_eh_context; #else static GThread::cotask comaintask(0); #endif maintask = &comaintask; // memset(maintask, 0, sizeof(GThread::cotask)); maintask->next = maintask; maintask->prev = maintask; gettimeofday(&time_base,NULL); curtask = maintask; } // allocation #ifndef NO_LIBGCC_HOOKS task = new GThread::cotask(stacksize,__new_eh_context()); #else task = new GThread::cotask(stacksize); #endif } GThread::~GThread() { if (task && task!=maintask) { if (task->prev) // running task->autodelete = true; else cotask_free(task); task = 0; } } #if __GNUC__ >= 3 # if __GNUC_MINOR__ >= 4 # define noinline __attribute__((noinline,used)) # elif __GNUC_MINOR >= 2 # define noinline __attribute__((noinline)) # endif #endif #ifndef noinline # define noinline /**/ #endif static noinline void startone(void); static noinline void starttwo(GThread *thr); static GThread * volatile starter; static void startone(void) { GThread *thr = starter; mach_switch(&thr->task->regs, &curtask->regs); // Registers may still contain an improper pointer // to the exception context. We should neither // register cleanups nor register handlers. starttwo(thr); abort(); } static void starttwo(GThread *thr) { // Hopefully this function reacquires // an exception context pointer. Therefore // we can register the exception handlers. // It is placed after ``startone'' to avoid inlining. #ifdef __EXCEPTIONS try { #endif G_TRY { thr->xentry( thr->xarg ); } G_CATCH(ex) { ex.perror(); DjVuMessageLite::perror( ERR_MSG("GThreads.uncaught") ); #ifdef _DEBUG abort(); #endif } G_ENDCATCH; #ifdef __EXCEPTIONS } catch(...) { DjVuMessageLite::perror( ERR_MSG("GThreads.unrecognized") ); #ifdef _DEBUG abort(); #endif } #endif cotask_terminate(curtask); GThread::yield(); // Do not add anything below this line! // Nothing should reach it anyway. abort(); } int GThread::create(void (*entry)(void*), void *arg) { if (task->next || task->prev) return -1; xentry = entry; xarg = arg; task->wchan = 0; task->next = curtask; task->prev = curtask->prev; task->next->prev = task; task->prev->next = task; GThread::cotask *old = curtask; starter = this; mach_start(&old->regs, (void*)startone, task->stack, task->stack+task->stacksize); if (scheduling_callback) (*scheduling_callback)(CallbackCreate); return 0; } void GThread::terminate() { if (task && task!=maintask) cotask_terminate(task); } int GThread::yield() { return cotask_yield(); } int GThread::select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) { return cotask_select(nfds, readfds, writefds, exceptfds, timeout); } void GThread::get_select(int &nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, unsigned long &timeout) { cotask_get_select(nfds, rfds, wfds, efds, timeout); } inline void * GThread::current() { if (curtask && curtask!=maintask) return (void*)curtask; return (void*)0; } // -------------------------------------- GMonitor GMonitor::GMonitor() : count(1), locker(0), wlock(0), wsig(0) { locker = 0; ok = 1; } GMonitor::~GMonitor() { ok = 0; cotask_wakeup((void*)&wsig, 0); cotask_wakeup((void*)&wlock, 0); cotask_yield(); // Because we know how the scheduler works, we know that this single call to // yield will run all unblocked tasks and given them the chance to leave the // scope of the monitor object. } void GMonitor::enter() { void *self = GThread::current(); if (count>0 || self!=locker) { while (ok && count<=0) { curtask->wchan = (void*)&wlock; wlock++; cotask_yield(); wlock--; } count = 1; locker = self; } count -= 1; } void GMonitor::leave() { void *self = GThread::current(); if (ok && (count>0 || self!=locker)) G_THROW( ERR_MSG("GThreads.not_acq_leave") ); if (++count > 0 && wlock > 0) cotask_wakeup((void*)&wlock, 1); } void GMonitor::signal() { void *self = GThread::current(); if (count>0 || self!=locker) G_THROW( ERR_MSG("GThreads.not_acq_signal") ); if (wsig > 0) { cotask_wakeup((void*)&wsig, 1); if (scheduling_callback) (*scheduling_callback)(GThread::CallbackUnblock); } } void GMonitor::broadcast() { void *self = GThread::current(); if (count>0 || self!=locker) G_THROW( ERR_MSG("GThreads.not_acq_broad") ); if (wsig > 0) { cotask_wakeup((void*)&wsig, 0); if (scheduling_callback) (*scheduling_callback)(GThread::CallbackUnblock); } } void GMonitor::wait() { // Check state void *self = GThread::current(); if (count>0 || locker!=self) G_THROW( ERR_MSG("GThreads.not_acq_wait") ); // Wait if (ok) { // Atomically release monitor and wait int sav_count = count; count = 1; curtask->wchan = (void*)&wsig; cotask_wakeup((void*)&wlock, 1); wsig++; cotask_yield(); wsig--; // Re-acquire while (ok && count <= 0) { curtask->wchan = (void*)&wlock; wlock++; cotask_yield(); wlock--; } count = sav_count; locker = self; } } void GMonitor::wait(unsigned long timeout) { // Check state void *self = GThread::current(); if (count>0 || locker!=self) G_THROW( ERR_MSG("GThreads.not_acq_wait") ); // Wait if (ok) { // Atomically release monitor and wait int sav_count = count; count = 1; unsigned long maxwait = time_elapsed(0) + timeout; curtask->maxwait = &maxwait; curtask->wchan = (void*)&wsig; cotask_wakeup((void*)&wlock, 1); wsig++; cotask_yield(); wsig--; // Re-acquire while (ok && count<=0) { curtask->wchan = (void*)&wlock; wlock++; cotask_yield(); wlock--; } count = sav_count; locker = self; } } #endif // ---------------------------------------- // GSAFEFLAGS // ---------------------------------------- GSafeFlags & GSafeFlags::operator=(long xflags) { enter(); if (flags!=xflags) { flags=xflags; broadcast(); } leave(); return *this; } GSafeFlags::operator long(void) const { long f; ((GSafeFlags *) this)->enter(); f=flags; ((GSafeFlags *) this)->leave(); return f; } bool GSafeFlags::test_and_modify(long set_mask, long clr_mask, long set_mask1, long clr_mask1) { enter(); if ((flags & set_mask)==set_mask && (~flags & clr_mask)==clr_mask) { long new_flags=flags; new_flags|=set_mask1; new_flags&=~clr_mask1; if (new_flags!=flags) { flags=new_flags; broadcast(); } leave(); return true; } leave(); return false; } void GSafeFlags::wait_and_modify(long set_mask, long clr_mask, long set_mask1, long clr_mask1) { enter(); while((flags & set_mask)!=set_mask || (~flags & clr_mask)!=clr_mask) wait(); long new_flags=flags; new_flags|=set_mask1; new_flags&=~clr_mask1; if (flags!=new_flags) { flags=new_flags; broadcast(); } leave(); } #ifdef HAVE_NAMESPACES } # ifndef NOT_USING_DJVU_NAMESPACE using namespace DJVU; # endif #endif