You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1888 lines
43 KiB
1888 lines
43 KiB
//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 <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
|
|
|
|
#if THREADMODEL==WINTHREADS
|
|
# 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();
|
|
}
|
|
|
|
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; 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, ©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<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(®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->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_tqmask, long clr_tqmask,
|
|
long set_tqmask1, long clr_tqmask1)
|
|
{
|
|
enter();
|
|
if ((flags & set_tqmask)==set_tqmask &&
|
|
(~flags & clr_tqmask)==clr_tqmask)
|
|
{
|
|
long new_flags=flags;
|
|
new_flags|=set_tqmask1;
|
|
new_flags&=~clr_tqmask1;
|
|
if (new_flags!=flags)
|
|
{
|
|
flags=new_flags;
|
|
broadcast();
|
|
}
|
|
leave();
|
|
return true;
|
|
}
|
|
leave();
|
|
return false;
|
|
}
|
|
|
|
void
|
|
GSafeFlags::wait_and_modify(long set_tqmask, long clr_tqmask,
|
|
long set_tqmask1, long clr_tqmask1)
|
|
{
|
|
enter();
|
|
while((flags & set_tqmask)!=set_tqmask ||
|
|
(~flags & clr_tqmask)!=clr_tqmask) wait();
|
|
long new_flags=flags;
|
|
new_flags|=set_tqmask1;
|
|
new_flags&=~clr_tqmask1;
|
|
if (flags!=new_flags)
|
|
{
|
|
flags=new_flags;
|
|
broadcast();
|
|
}
|
|
leave();
|
|
}
|
|
|
|
|
|
|
|
#ifdef HAVE_NAMESPACES
|
|
}
|
|
# ifndef NOT_USING_DJVU_NAMESPACE
|
|
using namespace DJVU;
|
|
# endif
|
|
#endif
|