Files
core/DjVuFile/libdjvu/GThreads.cpp
Oleg.Korshul 746ccb420b сборка под линукс. странные правки (добавление this->) - не править. Это особенность компилятора gcc
The problem is that templates are processed in two passes (according to the standard, VS does otherwise). In the first pass, before the type substitution, everything that does not depend on the template arguments is looked up and checked. Dependent names are then left to resolve in the second pass, once the type has been substituted. 

Now, in the first pass there is nothing that indicates that next is dependent on template arguments, and thus it needs to resolve before type substitution. Now, because the base type is templated on the template argument of your current template, the compiler cannot look into it (it might be specialized for some types, and without knowing what type T we are instantiating the template with, we cannot know which specialization to use, i.e. the base depends on T and we are checking before knowing T).

The trick of adding this-> turns next into a dependent name, and that in turn means that lookup is delayed until the second pass, where T is known, and because T is known, List<T> is also known and can be looked up into.


git-svn-id: svn://fileserver/activex/AVS/Sources/TeamlabOffice/trunk/ServerComponents@63683 954022d7-b5bf-4e40-9824-e11837661b57
2016-05-21 00:18:05 +03:00

1899 lines
45 KiB
C++

//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, either Version 2 of the license,
//C- or (at your option) any later version. 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 from
//C- Lizardtech Software. Lizardtech Software has authorized us to
//C- replace the original DjVu(r) Reference Library notice by the following
//C- text (see doc/lizard2002.djvu and doc/lizardtech2007.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, either Version 2 of the license,
//C- | or (at your option) any later version. 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.19 2007/03/25 20:48:32 leonb Exp $
// $Name: $
#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 <stdlib.h>
#include <stdio.h>
// ----------------------------------------
// 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
//< Changed for WinDjView project
//#if THREADMODEL==WINTHREADS
#if THREADMODEL==WINTHREADS || defined(WIN32_MONITOR)
//>
# include <process.h>
#endif
#if THREADMODEL==COTHREADS
# include <setjmp.h>
# include <string.h>
# include <unistd.h>
# include <sys/types.h>
# include <sys/time.h>
#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();
}
//< Changed for WinDjView project
#endif // THREADMODEL==WINTHREADS
#if THREADMODEL==WINTHREADS || defined(WIN32_MONITOR)
//>
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 (obsolete)
// ----------------------------------------
#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.
if (timeout > 0)
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; i<nfds; i++) if (FD_ISSET(i, &from->rset)) FD_SET(i, &dest->rset);
for (i=0; i<nfds; i++) if (FD_ISSET(i, &from->wset)) FD_SET(i, &dest->wset);
for (i=0; i<nfds; i++) if (FD_ISSET(i, &from->eset)) 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, &copyr, &copyw, &copye, &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<char> 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(&regs,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->maxwait<minwait)
minwait = *q->maxwait;
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->maxwait<minwait)
minwait = *q->maxwait;
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