/*****************************************************************************\
*                                                                             *
*  Name   : mutex                                                             *
*  Author : Chris Koeritz                                                     *
*                                                                             *
*******************************************************************************
* Copyright (c) 1996-$now By Author.  This program is free software; you can  *
* redistribute it and/or modify it under the terms of the GNU General Public  *
* License as published by the Free Software Foundation; either version 2 of   *
* the License or (at your option) any later version.  This is online at:      *
*     http://www.fsf.org/copyleft/gpl.html                                    *
* Please send any updates to: fred@gruntose.com                               *
\*****************************************************************************/

// NOTE: deadlock proofing for the linux implementation of recursive mutexes
// is guaranteed by locking order.  the guardian is locked either when the
// main mutex is already locked or when it is not locked already, but the
// main mutex is never locked while the guardian lock is possessed.

#include "mutex.h"
#include "portable.h"
#include "utility.h"

#ifdef __UNIX__
  #include <pthread.h>
#endif

mutex::mutex()
{
#ifdef __WIN32__
  _os_mutex = new CRITICAL_SECTION;
  InitializeCriticalSection((LPCRITICAL_SECTION)_os_mutex);
#elif defined(__UNIX__)
  _os_mutex = new pthread_mutex_t;
  _guardian = new pthread_mutex_t;
  _owner = 0;  // no owner yet.
  _count = 0;  // no lockers yet.
  pthread_mutex_init((pthread_mutex_t *)_os_mutex, NIL);
  pthread_mutex_init((pthread_mutex_t *)_guardian, NIL);
#else
  #pragma error("no implementation of mutexes for this OS yet!")
#endif
}

mutex::~mutex()
{
  defang();
}

void mutex::defang()
{
  if (!_os_mutex) return;  // already defunct.
#ifdef __WIN32__
  DeleteCriticalSection((LPCRITICAL_SECTION)_os_mutex);
  delete (CRITICAL_SECTION *)_os_mutex;
#elif defined(__UNIX__)
  pthread_mutex_destroy((pthread_mutex_t *)_guardian);
  delete (pthread_mutex_t *)_guardian;
  pthread_mutex_destroy((pthread_mutex_t *)_os_mutex);
  delete (pthread_mutex_t *)_os_mutex;
#else
  #pragma error("no implementation of mutexes for this OS yet!")
#endif
  _os_mutex = 0;
}

void mutex::lock()
{
  if (!_os_mutex) return;
#ifdef __WIN32__
  EnterCriticalSection((LPCRITICAL_SECTION)_os_mutex);
#elif defined(__UNIX__)
  // lock the guardian so we can check who the owner is.
  pthread_t id = pthread_self();
  pthread_mutex_lock((pthread_mutex_t *)_guardian);
  if (!_owner || (_owner != id) ) {
    // this thread is not the owner, so we can release the guardian.
    // we have assured that this thread is not re-entering the mutex.
    pthread_mutex_unlock((pthread_mutex_t *)_guardian);
    // now do a normal wait on the lock.  we know it's this thread's
    // first recent attempt.
    pthread_mutex_lock((pthread_mutex_t *)_os_mutex);
    // we now own the mutex; make sure we record the status.
    pthread_mutex_lock((pthread_mutex_t *)_guardian);
    _owner = id;
    _count = 1;
    pthread_mutex_unlock((pthread_mutex_t *)_guardian);
    return;
  } else {
    // this thread already owns the mutex.
    _count++;
    pthread_mutex_unlock((pthread_mutex_t *)_guardian);
  }
#else
  #pragma error("no implementation of mutexes for this OS yet!")
#endif
}

void mutex::unlock()
{
  if (!_os_mutex) return;
#ifdef __WIN32__
  LeaveCriticalSection((LPCRITICAL_SECTION)_os_mutex);
#elif defined(__UNIX__)
  // lock the guardian so we can change the counts and possibly the owner.
  pthread_mutex_lock((pthread_mutex_t *)_guardian);
  _count--;
  if (_count > 0) {
    // there are still other places where we own the main lock, so just leave.
    pthread_mutex_unlock((pthread_mutex_t *)_guardian);
    return;
  }
  _owner = NIL;
  // we are done with the main lock now.
  pthread_mutex_unlock((pthread_mutex_t *)_os_mutex);
  // release the guardian.
  pthread_mutex_unlock((pthread_mutex_t *)_guardian);
#else
  #pragma error("no implementation of mutexes for this OS yet!")
#endif
}

