//-< SYNC.H >--------------------------------------------------------*--------*
// GigaBASE                  Version 1.0         (c) 1999  GARRET    *     ?  *
// (Post Relational Database Management System)                      *   /\|  *
//                                                                   *  /  \  *
//                          Created:     20-Nov-98    K.A. Knizhnik  * / [] \ *
//                          Last update:  8-Feb-99    K.A. Knizhnik  * GARRET *
//-------------------------------------------------------------------*--------*
// Intertask synchonization primitives
//-------------------------------------------------------------------*--------*

#ifndef __SYNC_H__
#define __SYNC_H__


BEGIN_GIGABASE_NAMESPACE

#ifdef _WIN32
class GIGABASE_DLL_ENTRY dbMutex {
    CRITICAL_SECTION cs;
  public:
    dbMutex() {
	InitializeCriticalSection(&cs);
    }
    ~dbMutex() {
	DeleteCriticalSection(&cs);
    }
    void lock() {
	EnterCriticalSection(&cs);
    }
    void unlock() {
	LeaveCriticalSection(&cs);
    }
};

#define thread_proc WINAPI

class GIGABASE_DLL_ENTRY dbThread {
    HANDLE h;
  public:
    enum ThreadPriority { 
	THR_PRI_LOW, 
	THR_PRI_HIGH
    };

    void setPriority(ThreadPriority pri) { 
	SetThreadPriority(h, pri == THR_PRI_LOW ? THREAD_PRIORITY_IDLE : THREAD_PRIORITY_HIGHEST);
    }
	
    void create(void (thread_proc* f)(void*), void* arg) {
	DWORD threadid;
	h = CreateThread(NULL, NULL, LPTHREAD_START_ROUTINE(f), arg,
			 0, &threadid);
    }
    void join() {
	WaitForSingleObject(h, INFINITE);
	CloseHandle(h);
	h = NULL;
    }
    void detach() {
	if (h != NULL) {
	    CloseHandle(h);
	    h = NULL;
	}
    }
    dbThread() {
	h = NULL;
    }
    ~dbThread() {
	if (h != NULL) {
	    CloseHandle(h);
	}
    }
    static int numberOfProcessors() {
	SYSTEM_INFO sysinfo;
	GetSystemInfo(&sysinfo);
	return sysinfo.dwNumberOfProcessors;
    }
};

const int dbMaxSemValue = 1000000;


class GIGABASE_DLL_ENTRY dbSemaphore {
    HANDLE s;
  public:
    void wait(dbMutex& mutex, time_t timeout = INFINITE) {
	mutex.unlock();
	int rc = WaitForSingleObject(s, timeout == INFINITE ? timeout : timeout*1000);
	assert(rc == WAIT_OBJECT_0 || rc == WAIT_TIMEOUT);
	mutex.lock();
    }
    void signal(unsigned inc = 1) {
	if (inc != 0) {
	    ReleaseSemaphore(s, inc, NULL);
	}
    }
    void open(unsigned initValue = 0) {
	s = CreateSemaphore(NULL, initValue, dbMaxSemValue, NULL);
	assert(s != NULL);
    }
    void close() {
	CloseHandle(s);
    }
};

class GIGABASE_DLL_ENTRY dbEvent {
    HANDLE e;
    int    nWaitingThreads;
    int    nPulses;
  public:
    void wait(dbMutex& mutex, time_t timeout = INFINITE) {
	nWaitingThreads += 1;
	mutex.unlock();
	int rc = WaitForSingleObject(e, timeout == INFINITE ? timeout : timeout*1000);
	assert(rc == WAIT_OBJECT_0 || rc == WAIT_TIMEOUT);
	mutex.lock();
	nWaitingThreads -= 1;
	if (nPulses > 0) { 
	    nPulses -= 1;
	    ResetEvent(e);
	}
    }
    void signal() {
	SetEvent(e);
    }
    void reset() {
	ResetEvent(e);
    }
    void pulse() { 
	if (nWaitingThreads > 0) { 
	    nPulses += 1;
	    SetEvent(e);
	}
    }    
    void open(bool initValue = false) {
	e = CreateEvent(NULL, true, initValue, NULL);
	nWaitingThreads = 0;
	nPulses = 0;
    }
    void close() {
	CloseHandle(e);
    }
};

template<class T>
class dbThreadContext {
    int index;
  public:
    T* get() {
	return (T*)TlsGetValue(index);
    }
    void set(T* value) {
	TlsSetValue(index, value);
    }
    dbThreadContext() {
	index = TlsAlloc();
	assert(index != TLS_OUT_OF_INDEXES);
    }
    ~dbThreadContext() {
	TlsFree(index);
    }
};

#else // Unix

#define thread_proc

#ifndef NO_PTHREADS

#include <unistd.h>
#include <sys/time.h>
#include <pthread.h>

class dbMutex {
    friend class dbEvent;
    friend class dbSemaphore;
    pthread_mutex_t cs;
  public:
    dbMutex() {
	pthread_mutex_init(&cs, NULL);
    }
    ~dbMutex() {
	pthread_mutex_destroy(&cs);
    }
    void lock() {
	pthread_mutex_lock(&cs);
    }
    void unlock() {
	pthread_mutex_unlock(&cs);
    }
};


const size_t dbThreadStackSize = 1024*1024;

class dbThread {
    pthread_t thread;
  public:
    void create(void (thread_proc* f)(void*), void* arg) {
	pthread_attr_t attr;
	pthread_attr_init(&attr);
#if !defined(__linux__)
	pthread_attr_setstacksize(&attr, dbThreadStackSize);
#endif
#if defined(_AIX41)
	// At AIX 4.1, 4.2 threads are by default created detached
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_UNDETACHED);
#endif
	pthread_create(&thread, &attr, (void*(*)(void*))f, arg);
	pthread_attr_destroy(&attr);
    }

    enum ThreadPriority { 
	THR_PRI_LOW, 
	THR_PRI_HIGH
    };
    void setPriority(ThreadPriority pri) { 
#if defined(PRI_OTHER_MIN) && defined(PRI_OTHER_MAX)
	struct sched_param sp;
	sp.sched_priority = pri == THR_PRI_LOW ? IPRI_OTHER_MIN : PRI_OTHER_MAX;
	pthread_setschedparam(thread, SCHED_OTHER, &sp); 
#endif
    }



    void join() {
	void* result;
	pthread_join(thread, &result);
    }
    void detach() {
	pthread_detach(thread);
    }
    static int numberOfProcessors();
};

#if defined(_SC_NPROCESSORS_ONLN)
inline int dbThread::numberOfProcessors() {
    return sysconf(_SC_NPROCESSORS_ONLN);
}
#elif defined(__linux__)
#include <linux/smp.h>
inline int dbThread::numberOfProcessors() { return smp_num_cpus; }
#elif defined(__FreeBSD__) || defined(__bsdi__) || defined(__OpenBSD__)
#if defined(__bsdi__) || defined(__OpenBSD__)
#include <sys/param.h>
#endif
#include <sys/sysctl.h>
inline int dbThread::numberOfProcessors() {
    int mib[2],ncpus=0;
    size_t len=sizeof(ncpus);
    mib[0]= CTL_HW;
    mib[1]= HW_NCPU;
    sysctl(mib,2,&ncpus,&len,NULL,0);
    return ncpus;
}
#else
#warning Do not know how to detect number of processors: assuming 1
inline int dbThread::numberOfProcessors() { return 1; }
#endif

class dbEvent {
    pthread_cond_t   cond;
    int              signaled;
    long             n_signals;

  public:
    void wait(dbMutex& mutex) {
	long before_n_signals = n_signals;
	while (!signaled && n_signals == before_n_signals) {
	    pthread_cond_wait(&cond, &mutex.cs);
	}
    }

    void wait(dbMutex& mutex, time_t timeout) {
	long before_n_signals = n_signals;
	while (!signaled && n_signals == before_n_signals) {
	    struct timespec abs_ts; 
#ifdef PTHREAD_GET_EXPIRATION_NP
	    struct timespec rel_ts; 
	    rel_ts.tv_sec = timeout; 
	    rel_ts.tv_nsec = 0;
	    pthread_get_expiration_np(&rel_ts, &abs_ts);
#else
	    struct timeval cur_tv;
	    gettimeofday(&cur_tv, NULL);
	    abs_ts.tv_sec = cur_tv.tv_sec + timeout; 
	    abs_ts.tv_nsec = cur_tv.tv_usec;
#endif
	    pthread_cond_timedwait(&cond, &mutex.cs, &abs_ts);
	}
    }

    void signal() {
	signaled = true; 
	n_signals += 1;
	pthread_cond_broadcast(&cond);
    }

    void pulse() { 
	n_signals += 1;
	pthread_cond_broadcast(&cond);
    }
	
    void reset() {
	signaled = false;
    }
    void open(bool initValue = false) {
	signaled = initValue;
	pthread_cond_init(&cond, NULL);
    }
    void close() {
	pthread_cond_destroy(&cond);
    }
};

class dbSemaphore {
    pthread_cond_t   cond;
    int              count;
  public:
    void wait(dbMutex& mutex) {
	while (count == 0) {
	    pthread_cond_wait(&cond, &mutex.cs);
	}
	count -= 1;
    }

    void wait(dbMutex& mutex, time_t timeout) {
	while (count == 0) {
	    struct timespec abs_ts; 
#ifdef PTHREAD_GET_EXPIRATION_NP
	    struct timespec rel_ts; 
	    rel_ts.tv_sec = timeout; 
	    rel_ts.tv_nsec = 0;
	    pthread_get_expiration_np(&rel_ts, &abs_ts);
#else
	    struct timeval cur_tv;
	    gettimeofday(&cur_tv, NULL);
	    abs_ts.tv_sec = cur_tv.tv_sec + timeout; 
	    abs_ts.tv_nsec = cur_tv.tv_usec;
#endif
	    pthread_cond_timedwait(&cond, &mutex.cs, &abs_ts);
	}
	count -= 1;
    }

    void signal(unsigned inc = 1) {
	count += inc;
	if (inc > 1) {
	    pthread_cond_broadcast(&cond);
	} else if (inc == 1) {
	    pthread_cond_signal(&cond);
	}
    }
    void open(unsigned initValue = 0) {
	pthread_cond_init(&cond, NULL);
	count = initValue;
    }
    void close() {
	pthread_cond_destroy(&cond);
    }
};

template<class T>
class dbThreadContext {
    pthread_key_t key;
  public:
    T* get() {
	return (T*)pthread_getspecific(key);
    }
    void set(T* value) {
	pthread_setspecific(key, value);
    }
    dbThreadContext() {
        pthread_key_create(&key, NULL);
    }
    ~dbThreadContext() {
	pthread_key_delete(key);
    }
};

#else

class dbMutex {
   public:
    void lock() {}
    void unlock() {}
};

class dbThread {
  public:
    void create(void (thread_proc* f)(void*), void* arg) { f(arg); }
    void join() {}
    void detach() {}
    
    enum ThreadPriority { 
	THR_PRI_LOW, 
	THR_PRI_HIGH
    };
    void setPriority(ThreadPriority);

    static int numberOfProcessors() { return 1; }
};

class dbSemaphore {
    int count;
  public:
    void wait(dbMutex&, time_t=0) {
	assert (count > 0);
	count -= 1;
    }
    void signal(unsigned inc = 1) {
	count += inc;
    }
    void open(unsigned initValue = 0) {
	count = initValue;
    }
    void close() {}
};

class dbEvent {
    bool signaled;
  public:
    void wait(dbMutex&, time_t=0) {
	assert(signaled);
    }
    void signal() {
	signaled = true;
    }
    void reset() {
	signaled = false;
    }
    void open(bool initValue = false) {
	signaled = initValue;
    }
    void close() {}
};

template<class T>
class dbThreadContext {
    T* value;
  public:
    T* get() {
	return value;
    }
    void set(T* value) {
	this->value = value;
    }
    dbThreadContext() { value = NULL; }
};


#endif

#endif

class GIGABASE_DLL_ENTRY dbCriticalSection {
  private:
    dbMutex& mutex;
  public:
    dbCriticalSection(dbMutex& guard) : mutex(guard) {
	mutex.lock();
    }
    ~dbCriticalSection() {
	mutex.unlock();
    }
};

#define SMALL_BUF_SIZE 1024

template<class T>
class dbSmallBuffer {
  protected:
    T* buf;
    T  smallBuf[SMALL_BUF_SIZE];

  public:
    dbSmallBuffer(size_t size) {
	if (size > SMALL_BUF_SIZE) {
	    buf = new T[size];
	} else {
	    buf = smallBuf;
	}
    }

    operator T*() { return buf; }
    T* base() { return buf; }

    ~dbSmallBuffer() {
	if (buf != smallBuf) {
	    delete[] buf;
	}
    }
};

END_GIGABASE_NAMESPACE

#endif
