/* Copyright (C) 2005 to 2013 Chris Vine

The library comprised in this file or of which this file is part is
distributed by Chris Vine under the GNU Lesser General Public
License as follows:

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public License
   as published by the Free Software Foundation; either version 2.1 of
   the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License, version 2.1, for more details.

   You should have received a copy of the GNU Lesser General Public
   License, version 2.1, along with this library (see the file LGPL.TXT
   which came with this source code package in the c++-gtk-utils
   sub-directory); if not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

However, it is not intended that the object code of a program whose
source code instantiates a template from this file or uses macros or
inline functions (of any length) should by reason only of that
instantiation or use be subject to the restrictions of use in the GNU
Lesser General Public License.  With that in mind, the words "and
macros, inline functions and instantiations of templates (of any
length)" shall be treated as substituted for the words "and small
macros and small inline functions (ten lines or less in length)" in
the fourth paragraph of section 5 of that licence.  This does not
affect any other reason why object code may be subject to the
restrictions in that licence (nor for the avoidance of doubt does it
affect the application of section 2 of that licence to modifications
of the source code in this file).

*/

#ifndef CGU_MUTEX_H
#define CGU_MUTEX_H

#include <exception>
#include <pthread.h>
#include <time.h>

#include <glib.h>
#include <c++-gtk-utils/cgu_config.h>

/**
 * @file mutex.h
 * @brief Provides wrapper classes for pthread mutexes and condition
 * variables, and scoped locking classes for exception safe mutex
 * locking.
 * @note If the system supports monotonic clocks (and this library is
 * not being cross-compiled onto a different architecture), then a
 * system monotonic clock will be used in
 * Cgu::Thread::Cond::timed_wait() and
 * Cgu::Thread::Cond::get_abs_time().  This can be tested at run time
 * with Cgu::Thread::Cond::have_monotonic_clock().
 */

namespace Cgu {

namespace Thread {

struct CondError: public std::exception {
  virtual const char* what() const throw() {return "Thread::CondError";}
};

/*
 * Since version 1.2.0, which automatically checks for monotonic
 * clocks in its configure script, this exception is no longer thrown.
 * We keep the class just for source compatibility purposes.
 */
#ifndef DOXYGEN_PARSING
struct CondSetClockError: public std::exception {
  virtual const char* what() const throw() {return "Thread::CondSetClockError";}
};
#endif

struct MutexError: public std::exception {
  virtual const char* what() const throw() {return "Thread::MutexError";}
};

struct RecMutexError: public std::exception {
  virtual const char* what() const throw() {return "Thread::RecMutexError";}
};

class Cond;

/**
 * @class Mutex mutex.h c++-gtk-utils/mutex.h
 * @brief A wrapper class for pthread mutexes.
 * @sa Thread::Thread Thread::Mutex::Lock Thread::Mutex::TrackLock Thread::Cond Thread::RecMutex
 *
 * This class can be used interchangeably with threads started with
 * GThread and by this library, as both glib and this library use
 * pthreads underneath on POSIX and other unix-like OSes.  It can also
 * be used interchangeably with those started by C++11 and with
 * std::mutex and similar objects, as in C++11 on unix-like OSes these
 * facilities will be built on top of pthreads (for which purpose
 * C++11 provides the std::native_handle_type type and
 * std::thread::native_handle() function), or if they are not, they
 * will use the same threading primitives provided by the kernel.
 *
 * Mutex objects can be constructed statically as well as dynamically
 * and there is no need to call g_thread_init() before they are
 * constructed, even if glib < 2.32 is used.  (If created as a static
 * object in global scope, it will not be possible to catch
 * Thread::MutexError thrown by its constructor, but if a static
 * global mutex throws there is nothing that could be done anyway
 * except abort, and it would show that the pthreads installation is
 * seriously defective.)
 */

class Mutex {
  pthread_mutex_t pthr_mutex;
  
public:
  class Lock;
  class TrackLock;
  friend class Cond;

/**
 * This class cannot be copied.  The copy constructor is deleted.
 */
  Mutex(const Mutex&) = delete;

/**
 * This class cannot be copied.  The assignment operator is deleted.
 */
  Mutex& operator=(const Mutex&) = delete;

/**
 * Locks the mutex and acquires ownership.  Blocks if already locked
 * until it becomes free.  It is not a cancellation point.  It does
 * not throw.  It is thread safe.
 * @return 0 if successful, otherwise the pthread mutex error number.
 * @note With this library implementation, the only pthread error
 * number which could be returned by this method is EDEADLK, which it
 * would do if the default pthread mutex behaviour happens to return
 * that error rather than deadlock in the case of recursive locking.
 * Most default implementations do not do this and hence the return
 * value is usually not worth checking for except during debugging.
 */
  int lock() noexcept {return pthread_mutex_lock(&pthr_mutex);}

/**
 * Tries to lock the mutex and acquire ownership, but returns
 * immediately if it is already locked with value EBUSY.  It is not a
 * cancellation point.  It does not throw.  It is thread safe.
 * @return 0 if successful, otherwise EBUSY.
 * @note With this library implementation, the only pthread error
 * number which could be returned by this method is EBUSY.
 */
  int trylock() noexcept {return pthread_mutex_trylock(&pthr_mutex);}

/**
 * Unlocks a locked mutex owned by the calling thread and relinquishes
 * ownership.  It is not a cancellation point.  It does not throw.  It
 * must be called by the thread which owns the mutex.
 * @return 0 if successful, otherwise the pthread mutex error number.
 * @note With this library implementation, the only pthread error
 * number which could be returned by this method is EPERM because the
 * calling thread does not own the mutex (however POSIX does not
 * require that return value in that case and hence the return value
 * is usually not worth checking for except during debugging).
 */
  int unlock() noexcept {return pthread_mutex_unlock(&pthr_mutex);}

/**
 * Initialises the pthread mutex.  It is not a cancellation point.
 * @exception Cgu::Thread::MutexError Throws this exception if
 * initialization of the mutex fails.  (It is often not worth checking
 * for this, as it means either memory is exhausted or pthread has run
 * out of other resources to create new mutexes.)
 */
  Mutex() {if (pthread_mutex_init(&pthr_mutex, 0)) throw MutexError();}

/**
 *  Destroys the pthread mutex.  It is not a cancellation point.  It
 *  does not throw.  Destroying a mutex which is currently locked or
 *  associated with an active condition variable wait results in
 *  undefined behavior.
 */
  ~Mutex() {pthread_mutex_destroy(&pthr_mutex);}

#ifdef CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT
  CGU_GLIB_MEMORY_SLICES_FUNCS
#endif
};

// used as a second argument to Lock::Lock() and TrackLock::TrackLock
// in cases where the mutex has already been locked (say by Mutex::trylock())
enum Locked {locked};
// used as a second argument to TrackLock::TrackLock() in cases where
// locking of the mutex is to be deferred
enum DeferLock {defer};

/**
 * @class Mutex::Lock mutex.h c++-gtk-utils/mutex.h
 * @brief A scoped locking class for exception safe Mutex locking.
 * @sa Thread::Mutex Thread::Mutex::TrackLock Thread::Thread Thread::Cond
 */

class Mutex::Lock {
  Mutex& mutex;

public:
  friend class Cond;

/**
 * This class cannot be copied.  The copy constructor is deleted.
 */
  Lock(const Mutex::Lock&) = delete;

/**
 * This class cannot be copied.  The assignment operator is deleted.
 */
  Mutex::Lock& operator=(const Mutex::Lock&) = delete;

/**
 * Calls Mutex::lock(), and so locks the mutex and reacquires
 * ownership.  It blocks if the mutex is already locked until the
 * mutex becomes free.  This method should normally only be called if
 * a previous call has been made to Mutex::Lock::unlock() (that is,
 * where the thread owning the Mutex::Lock object has temporarily
 * allowed another thread to take the mutex concerned).  It is not a
 * cancellation point.  It does not throw.
 * @return 0 if successful, otherwise the pthread mutex error number.
 * @note With this library implementation, the only pthread error
 * number which could be returned by this method is EDEADLK, which it
 * would do if the default pthread mutex behaviour happens to return
 * that error rather than deadlock in the case of recursive locking.
 * Most default implementations do not do this and hence the return
 * value is usually not worth checking for except during debugging.
 * @sa Mutex::TrackLock.
 */
  int lock() noexcept {return mutex.lock();}

/**
 * Calls Mutex::trylock(), and so tries to lock the mutex and
 * reacquire ownership, but returns immediately if it is already
 * locked with value EBUSY.  This method should normally only be
 * called if a previous call has been made to Mutex::Lock::unlock()
 * (that is, where the thread owning the Mutex::Lock object has
 * temporarily allowed another thread to take the mutex concerned).
 * It is not a cancellation point.  It does not throw.
 * @return 0 if successful, otherwise EBUSY.
 * @note With this library implementation, the only pthread error
 * number which could be returned by this method is EBUSY.
 * @sa Mutex::TrackLock.
 */
  int trylock() noexcept {return mutex.trylock();}

/**
 * Calls Mutex::unlock(), and so unlocks a locked mutex owned by the
 * calling thread and relinquishes ownership (so temporarily allowing
 * another thread to take the mutex).  This method should normally
 * only be called if it is to be followed by a call to
 * Mutex::Lock::lock() or a successful call to Mutex::Lock::trylock()
 * before the Mutex::Lock object concerned goes out of scope
 * (otherwise Mutex::Lock's destructor will attempt to unlock an
 * already unlocked mutex or a mutex of which another thread has by
 * then taken ownership - Mutex::Lock objects do not maintain state).
 * See Mutex::TrackLock::unlock() for a safe version of this method.
 * It is not a cancellation point.  It does not throw.
 * @return 0 if successful, otherwise the pthread mutex error number.
 * @note With this library implementation, the only pthread error
 * number which could be returned by this method is EPERM because the
 * calling thread does not own the mutex (however POSIX does not
 * require that return value in that case and hence the return value
 * is usually not worth checking for except during debugging).
 * @sa Mutex::TrackLock.
 */
  int unlock() noexcept {return mutex.unlock();}

/**
 * This constructor locks the mutex passed to it.  It is not a
 * cancellation point.  It does not throw.
 * @param mutex_ The mutex to be locked.
 */
  Lock(Mutex& mutex_) noexcept: mutex(mutex_) {mutex.lock();}

/**
 * This constructor takes an already locked mutex (say as a result of
 * Mutex::trylock()), and takes ownership of it.  It is not a
 * cancellation point.  It does not throw.
 * @param mutex_ The mutex to be managed by this object.
 * @param tag Pass the Cgu::Thread::locked enum tag to this parameter.
 */
  Lock(Mutex& mutex_, Locked tag) noexcept: mutex(mutex_) {}

/**
 * This class requires initialisation with a Mutex.  The default
 * constructor is deleted.
 */
  Lock() = delete;

/**
 * The destructor unlocks the owned mutex.  It is not a cancellation
 * point.  It does not throw.
 */
  ~Lock() {mutex.unlock();}

/* Only has effect if --with-glib-memory-slices-compat or
 * --with-glib-memory-slices-no-compat option picked */
  CGU_GLIB_MEMORY_SLICES_FUNCS
};

/**
 * @class Mutex::TrackLock mutex.h c++-gtk-utils/mutex.h
 * @brief A scoped locking class for exception safe Mutex locking
 * which tracks the status of its mutex.
 * @sa Thread::Mutex Thread::Mutex::Lock Thread::Thread Thread::Cond
 *
 * This class is similar to a Mutex::Lock object, except that it
 * tracks whether the mutex it manages is locked by the thread
 * creating the Mutex::TrackLock object (provided that, while the
 * Mutex::TrackLock object exists, the thread creating it only
 * accesses the mutex through that object).  This enables
 * Mutex::TrackLock::unlock() to be used without it being followed
 * later by a call to Mutex::TrackLock::lock() or a successful call to
 * Mutex::TrackLock::trylock(), and also permits locking to be
 * deferred until after construction of the lock object.  Note that
 * only one thread may call the methods of any one Mutex::TrackLock
 * object, including causing its destructor to be invoked.
 */

class Mutex::TrackLock {
  Mutex& mutex;
  bool owner;

public:
  friend class Cond;

/**
 * This class cannot be copied.  The copy constructor is deleted.
 */
  TrackLock(const Mutex::TrackLock&) = delete;

/**
 * This class cannot be copied.  The assignment operator is deleted.
 */
  Mutex::TrackLock& operator=(const Mutex::TrackLock&) = delete;

/**
 * Calls Mutex::lock(), and so locks the mutex and acquires ownership.
 * It blocks if the mutex is already locked until the mutex becomes
 * free.  This method should normally only be called if a previous
 * call has been made to Mutex::TrackLock::unlock() or this
 * Mutex::TrackLock object has been constructed with the Thread::defer
 * enum tag.  It is not a cancellation point.  It does not throw.
 * @return 0 if successful, otherwise the pthread mutex error number.
 * @note With this library implementation, the only pthread error
 * number which could be returned by this method is EDEADLK, which it
 * would do if the default pthread mutex behaviour happens to return
 * that error rather than deadlock in the case of recursive locking.
 * Most default implementations do not do this and hence the return
 * value is usually not worth checking for except during debugging.
 */
  int lock() noexcept {int ret = mutex.lock(); if (!owner) owner = !ret; return ret;}

/**
 * Calls Mutex::trylock(), and so tries to lock the mutex and acquire
 * ownership, but returns immediately if it is already locked with
 * value EBUSY.  This method should normally only be called if a
 * previous call has been made to Mutex::TrackLock::unlock() or this
 * Mutex::TrackLock object has been constructed with the Thread::defer
 * enum tag.  It is not a cancellation point.  It does not throw.
 * @return 0 if successful, otherwise EBUSY.
 * @note With this library implementation, the only pthread error
 * number which could be returned by this method is EBUSY.
 */
  int trylock() noexcept {int ret = mutex.trylock(); if (!owner) owner = !ret; return ret;}

/**
 * Calls Mutex::unlock(), and so unlocks a locked mutex owned by the
 * calling thread.  It will cause is_owner() to return false unless a
 * subsequent call is made to lock() or a subsequent successful call
 * is made to trylock().  It is not a cancellation point.  It does not
 * throw.
 * @return 0 if successful, otherwise the pthread mutex error number.
 * @note With this library implementation, the only pthread error
 * number which could be returned by this method is EPERM because the
 * calling thread does not own the mutex (however POSIX does not
 * require that return value in that case and hence the return value
 * is usually not worth checking for except during debugging).
 */
  int unlock() noexcept {int ret = mutex.unlock(); if (owner) owner = ret; return ret;}

/**
 * Indicates whether the mutex managed by this Mutex::TrackLock object
 * is locked, and so owned, by it.  It does not throw.
 * @return true if the mutex is locked by this object, otherwise
 * false.
 */
  bool is_owner() const noexcept {return owner;}

/**
 * This constructor locks the mutex passed to it.  It is not a
 * cancellation point.  It does not throw.
 * @param mutex_ The mutex to be locked.
 */
  TrackLock(Mutex& mutex_) noexcept: mutex(mutex_), owner(true) {mutex.lock();}

/**
 * This constructor takes an already locked mutex (say as a result of
 * Mutex::trylock()), and takes ownership of it.  It is not a
 * cancellation point.  It does not throw.
 * @param mutex_ The mutex to be managed by this object.
 * @param tag Pass the Cgu::Thread::locked enum tag to this parameter.
 */
  TrackLock(Mutex& mutex_, Locked tag) noexcept: mutex(mutex_), owner(true) {}

/**
 * This constructor defers locking of the mutex (and so taking
 * ownership of it) until an explicit call to lock() or trylock() is
 * made.  It is not a cancellation point.  It does not throw.
 * @param mutex_ The mutex to be managed by this object.
 * @param tag Pass the Cgu::Thread::defer enum tag to this parameter.
 */
  TrackLock(Mutex& mutex_, DeferLock tag) noexcept: mutex(mutex_), owner(false) {}

/**
 * This class requires initialisation with a Mutex.  The default
 * constructor is deleted.
 */
  TrackLock() = delete;

/**
 * The destructor unlocks the managed mutex if it is locked.  It is
 * not a cancellation point.  It does not throw.
 */
  ~TrackLock() {if (owner) mutex.unlock();}

/* Only has effect if --with-glib-memory-slices-compat or
 * --with-glib-memory-slices-no-compat option picked */
  CGU_GLIB_MEMORY_SLICES_FUNCS
};

/**
 * @class Cond mutex.h c++-gtk-utils/mutex.h
 * @brief A wrapper class for pthread condition variables.
 * @sa Thread::Thread Thread::Mutex Thread::Mutex::Lock Thread::Mutex::TrackLock
 */

class Cond {
  pthread_cond_t cond;

public:

/**
 * This class cannot be copied.  The copy constructor is deleted.
 */
  Cond(const Cond&) = delete;

/**
 * This class cannot be copied.  The assignment operator is deleted.
 */
  Cond& operator=(const Cond&) = delete;

/**
 * Unblock at least one thread waiting on this condition variable.
 * Can be called by any thread. It is not a cancellation point.  Does
 * not throw.
 * @return 0 if successful, otherwise the pthread error number.
 * @note With this library implementation, no pthread error should
 * arise so there is no need to check the return value.
 */
  int signal() noexcept {return pthread_cond_signal(&cond);}

/**
 * Unblocks all threads waiting on this condition variable, which
 * acquire the mutex in an order determined by the scheduling policy.
 * Can be called by any thread. It is not a cancellation point.  Does
 * not throw.
 * @return 0 if successful, otherwise the pthread error number.  
 * @note With this library implementation, no pthread error should
 * arise so there is no need to check the return value.
 */
  int broadcast() noexcept {return pthread_cond_broadcast(&cond);}

/**
 * Waits on this condition variable until awoken.  It must be called
 * by the thread which owns the mutex.  Re-acquires the mutex when
 * awoken.  It is a cancellation point.  This method is cancellation
 * safe even if the stack does not unwind on cancellation (but if the
 * stack does not unwind some other way of destroying this object on
 * cancellation is required, such as by having it allocated on
 * freestore and deleted in a cancellation clean-up handler).  This
 * method does not throw.
 * @param mutex The locked mutex associated with the wait which is
 * re-acquired on being awoken.
 * @return 0 after being awoken on waiting, otherwise the pthread
 * error number.
 * @note 1. pthread condition variables can, as a matter of design,
 * awake spontaneously (and Cond::signal() may awaken more than one
 * thread).  Therefore the relevant condition should be tested in a
 * while loop and not in an if block.  0 will be returned on a
 * spontaneous awakening.
 * @note 2. With this library implementation, the only pthread error
 * numbers which could be returned are EINVAL (if the mutex argument
 * is not a valid mutex) or EPERM (if the thread calling this method
 * does not own the mutex).
 */
  int wait(Mutex& mutex) noexcept {return pthread_cond_wait(&cond, &mutex.pthr_mutex);}

/**
 * Does the same as Cond::wait(Mutex&), except that as a convenience
 * it will take a Mutex::Lock object handling the Mutex object as an
 * alternative to passing the Mutex object itself.
 */
  int wait(Mutex::Lock& lock) noexcept {return wait(lock.mutex);}

/**
 * Does the same as Cond::wait(Mutex&), except that as a convenience
 * it will take a Mutex::TrackLock object handling the Mutex object as
 * an alternative to passing the Mutex object itself.
 */
  int wait(Mutex::TrackLock& lock) noexcept {return wait(lock.mutex);}

/**
 * Waits on this condition variable until awoken (in which case it
 * re-acquires the mutex), or until the timeout expires (in which case
 * it re-acquires the mutex and returns with ETIMEDOUT).  It must be
 * called by the thread which owns the mutex. Re-acquires the mutex
 * when awoken or timing out.  It is a cancellation point.  This
 * method is cancellation safe even if the stack does not unwind on
 * cancellation (but if the stack does not unwind some other way of
 * destroying this object on cancellation is required, such as by
 * having it allocated on freestore and deleted in a cancellation
 * clean-up handler).  This method does not throw.
 * @param mutex The locked mutex associated with the wait which is
 * re-acquired on being awoken or timing out.
 * @param abs_time The time at which the wait will unblock if not
 * previously awoken.  A suitable value can be obtained by calling
 * the get_abs_time() function.
 * @return 0 after being awoken on waiting, otherwise ETIMEDOUT or
 * other pthread error number.
 * @note 1. With this library implementation, apart from ETIMEDOUT,
 * the only pthread error numbers which could be returned are EINVAL
 * (if the mutex argument is not a valid mutex or the abs_time
 * argument does not comprise a valid timespec struct) or EPERM (if
 * the thread calling this method does not own the mutex).
 * @note 2. pthread condition variables can, as a matter of design,
 * awake spontaneously (and Cond::signal() may awaken more than one
 * thread).  Therefore the relevant condition should be tested in a
 * while loop and not in an if block.  0 will be returned on a
 * spontaneous awakening.
 * @note 3. If the system supports monotonic clocks (and this library
 * is not being cross-compiled onto a different architecture), then
 * condition variables will use a monotonic clock in
 * Cond::timed_wait() and Cond::get_abs_time().  This can be tested at
 * run time with Cond::have_monotonic_clock().
 */
  int timed_wait(Mutex& mutex, const timespec& abs_time) noexcept {
    return pthread_cond_timedwait(&cond, &mutex.pthr_mutex, &abs_time);
  }

/**
 * Does the same as Cond::timed_wait(Mutex&, const timespec&), except
 * that as a convenience it will take a Mutex::Lock object handling
 * the Mutex object as an alternative to passing the Mutex object
 * itself.
 */
  int timed_wait(Mutex::Lock& lock,
		 const timespec& abs_time) noexcept {return timed_wait(lock.mutex, abs_time);}

/**
 * Does the same as Cond::timed_wait(Mutex&, const timespec&), except
 * that as a convenience it will take a Mutex::TrackLock object
 * handling the Mutex object as an alternative to passing the Mutex
 * object itself.
 */
  int timed_wait(Mutex::TrackLock& lock,
		 const timespec& abs_time) noexcept {return timed_wait(lock.mutex, abs_time);}

/**
 * This is a utility function that inserts into a timespec structure
 * the current time plus a given number of milliseconds ahead, which
 * can be applied to a call to Cond::timed_wait().  It does not throw.
 * It is thread-safe.
 * @param ts A timespec object into which the result of current time +
 * millisec will be placed.
 * @param millisec The number of milliseconds ahead of current time to
 * which the timespec object will be set.
 * @note If the system supports monotonic clocks (and this library is
 * not being cross-compiled onto a different architecture), then
 * condition variables will use a system monotonic clock in this
 * method and Cond::timed_wait().  This can be tested at run time with
 * Cond::have_monotonic_clock().
 */
  static void get_abs_time(timespec& ts, unsigned int millisec);

/**
 * Indicates whether the library has been compiled with support for
 * monotonic clocks in Cond::timed_wait().  Most recent linux and BSD
 * distributions will support them, and this function would normally
 * return true unless the library has been cross-compiled from one
 * platform to a different platform.  This function can be tested at
 * program initialization, and if they are not supported a warning can
 * be given to the user about the deficiences of using the system
 * clock for timed events.  It does not throw.  It is thread safe.
 * @return true if the library has been compiled with support for
 * monotonic clocks in Cond::timed_wait(), otherwise false.
 */
  static bool have_monotonic_clock();

/**
 * Initialises the pthread condition variable.  It is not a
 * cancellation point.
 * @exception Cgu::Thread::CondError Throws this exception if
 * initialization of the condition variable fails.  (It is often not
 * worth checking for CondError, as it means either memory is
 * exhausted or pthread has run out of other resources to create new
 * condition variables.)
 * @note If the system supports monotonic clocks (and this library is
 * not being cross-compiled onto a different architecture), then
 * condition variables will use a system monotonic clock in
 * Cond::timed_wait() and Cond::get_abs_time().  This can be tested at
 * run time by calling Cond::have_monotonic_clock().
 */
  Cond();

/**
 * Destroys the pthread condition variable. It is not a cancellation
 * point.  The destructor does not throw.  Destroying a condition
 * variable on which another thread is currently blocked results in
 * undefined behavior.
 */
  ~Cond(void) {pthread_cond_destroy(&cond);}

/* Only has effect if --with-glib-memory-slices-compat or
 * --with-glib-memory-slices-no-compat option picked */
  CGU_GLIB_MEMORY_SLICES_FUNCS
};

/**
 * @class RecMutex mutex.h c++-gtk-utils/mutex.h
 * @brief A wrapper class for pthread mutexes which provides a
 * recursive mutex.
 * @sa Thread::Thread Thread::RecMutex::Lock Thread::RecMutex::TrackLock Thread::Mutex
 *
 * This class can be used interchangeably with threads started with
 * GThread and by this library, as both glib and this library use
 * pthreads underneath on POSIX and other unix-like OSes.  It can also
 * be used interchangeably with those started by C++11 and with
 * std::recursive_mutex and similar objects, as in C++11 on unix-like
 * OSes these facilities will be built on top of pthreads (for which
 * purpose C++11 provides the std::native_handle_type type and
 * std::thread::native_handle() function), or if they are not, they
 * will use the same threading primitives provided by the kernel.
 *
 * RecMutex objects can be constructed statically as well as
 * dynamically and there is no need to call g_thread_init() before
 * they are constructed, even if glib < 2.32 is used.  (If created as
 * a static object in global scope, it will not be possible to catch
 * Thread::MutexError or Thread::RecMutexError thrown by its
 * constructor, but if a static global mutex throws there is nothing
 * that could be done anyway except abort.)
 *
 * See the comments below on the test_support() method of this class,
 * about how the system's support for native recursive mutexes can be
 * tested at runtime and when a user program is compiled.  If glib >=
 * 2.32 is installed, it can be assumed that native recursive mutexes
 * are available, as glib >= 2.32 also uses them.
 */

class RecMutex {
  pthread_mutex_t pthr_mutex;
  
public:
  class Lock;
  class TrackLock;

/**
 * This class cannot be copied.  The copy constructor is deleted.
 */
  RecMutex(const RecMutex&) = delete;

/**
 * This class cannot be copied.  The assignment operator is deleted.
 */
  RecMutex& operator=(const RecMutex&) = delete;

/**
 * Locks the mutex and acquires ownership.  Blocks if already locked
 * until it becomes free, unless the calling thread already holds the
 * lock, in which case it increments the lock count and returns
 * immediately.  It is not a cancellation point.  It does not throw.
 * It is thread safe.
 * @return 0 if successful, otherwise the pthread mutex error number.
 * @note With this library implementation, the only pthread error
 * number which could be returned by this method is EAGAIN, which it
 * would do if the maximum recursive lock count for this mutex has
 * been reached.  Usually this number is at or around INT_MAX and
 * hence the return value is usually not worth checking for except
 * during debugging.
 */
  int lock() noexcept {return pthread_mutex_lock(&pthr_mutex);}

/**
 * Tries to lock the mutex and acquire ownership, but returns
 * immediately if it is already locked with value EBUSY unless the
 * calling thread already holds the lock, in which case it returns
 * normally and increments the lock count.  It is not a cancellation
 * point.  It does not throw.  It is thread safe.
 * @return 0 if successful, otherwise EBUSY or other pthread mutex
 * error number.
 * @note With this library implementation, apart from EBUSY, the only
 * other pthread error number which could be returned by this method
 * is EAGAIN, which it would do if the maximum recursive lock count
 * for this mutex has been reached.  Usually this number is at or
 * around INT_MAX and hence an EAGAIN error return value is usually
 * not worth checking for except during debugging.
 */
  int trylock() noexcept {return pthread_mutex_trylock(&pthr_mutex);}

/**
 * Unlocks a locked mutex owned by the calling thread and either
 * relinquishes ownership (if the mutex has not been recursively
 * locked) or decrements the lock count (if it has).  It is not a
 * cancellation point.  It does not throw.  It must be called by the
 * thread which owns the mutex.
 * @return 0 if successful, otherwise the pthread mutex error number.  
 * @note With this library implementation, the only pthread error
 * number which could be returned by this method is EPERM because the
 * calling thread does not own the mutex (however POSIX does not
 * require that return value in that case and hence the return value
 * is usually not worth checking for except during debugging).
 */
  int unlock() noexcept {return pthread_mutex_unlock(&pthr_mutex);}

/**
 * Indicates whether the system supports recursive mutexes.  This
 * method does not throw.  It is thread safe.
 * @return 0 if the system supports recursive mutexes, -1 if it does
 * not support recursive mutexes and 1 if pthread has run out of
 * sufficient resources to test this at run time (in which case any
 * attempt to create mutexes or start threads is likely to fail).
 * Practically all recent linux and BSD distributions will support
 * them, and this function would normally return 0.  If it does not,
 * it is still possible to use GStaticRecMutex objects (for which
 * purpose see Cgu::Thread::GrecmutexLock).
 *
 * @note The header file <c++-gtk-utils/cgu_config.h> defines the
 * symbol CGU_HAVE_RECURSIVE_MUTEX if native recursive mutexes were
 * found to be supported when this library was compiled.  Programs
 * using this library can therefore test for that symbol with the
 * pre-processor for conditional compilation purposes (so that the
 * program can, for example, be compiled to use GStaticRecMutex if
 * that symbol is not defined).  However, if the library was
 * cross-compiled from one architecture to another, that symbol may
 * not be defined even though the target architecture does, in fact,
 * support them at program runtime.  In other words, if
 * CGU_HAVE_RECURSIVE_MUTEX is defined then this method will always
 * return 0; but in the event of cross-compilation of the library this
 * method (which provides a separate runtime test) might return 0,
 * correctly showing support, even when CGU_HAVE_RECURSIVE_MUTEX is
 * not defined.
 *
 * @note If glib >= 2.32 is installed, it can be assumed that native
 * recursive mutexes are available, as glib >= 2.32 also uses them.
 */
  static int test_support();

/**
 * Initialises the pthread mutex.  It is not a cancellation point.
 * @exception Cgu::Thread::RecMutexError Throws this exception if the
 * system does not support recursive mutexes.  (If this has been
 * checked beforehand, say by calling test_support(), there should be
 * no need to check for this exception.)
 * @exception Cgu::Thread::MutexError Throws this exception if
 * initialization of the mutex fails, except because the system does
 * not support recursive mutexes.  (It is often not worth checking for
 * MutexError, as it means either memory is exhausted or pthread has
 * run out of other resources to create new mutexes.)
 */
  RecMutex();

/**
 *  Destroys the pthread mutex.  It is not a cancellation point.  It
 *  does not throw.  Destroying a mutex which is currently locked
 *  results in undefined behavior.
 */
  ~RecMutex() {pthread_mutex_destroy(&pthr_mutex);}

/* Only has effect if --with-glib-memory-slices-compat or
 * --with-glib-memory-slices-no-compat option picked */
  CGU_GLIB_MEMORY_SLICES_FUNCS
};

/**
 * @class RecMutex::Lock mutex.h c++-gtk-utils/mutex.h
 * @brief A scoped locking class for exception safe RecMutex locking.
 * @sa Thread::RecMutex Thread::RecMutex::TrackLock Thread::Thread
 */

class RecMutex::Lock {
  RecMutex& mutex;

public:
/**
 * This class cannot be copied.  The copy constructor is deleted.
 */
  Lock(const RecMutex::Lock&) = delete;

/**
 * This class cannot be copied.  The assignment operator is deleted.
 */
  RecMutex::Lock& operator=(const RecMutex::Lock&) = delete;

/**
 * This calls RecMutex::lock(), and so locks the mutex and reacquires
 * ownership.  It blocks if the mutex is already locked until the
 * mutex becomes free, unless the calling thread already holds the
 * lock, in which case it increments the lock count and returns
 * immediately.  This method should normally only be called if a
 * previous call has been made to RecMutex::Lock::unlock() (that is,
 * where the thread owning the RecMutex::Lock object has temporarily
 * allowed another thread to take the mutex concerned if it is not
 * recursively locked).  It is not a cancellation point.  It does not
 * throw.
 * @return 0 if successful, otherwise the pthread mutex error number.
 * @note With this library implementation, the only pthread error
 * number which could be returned by this method is EAGAIN, which it
 * would do if the maximum recursive lock count for the particular
 * mutex in question has been reached.  Usually this number is at or
 * around INT_MAX and hence the return value is usually not worth
 * checking for except during debugging.
 * @sa RecMutex::TrackLock.
 */
  int lock() noexcept {return mutex.lock();}

/**
 * This calls RecMutex::trylock(), and so tries to lock the mutex and
 * reacquire ownership, but returns immediately if it is already
 * locked with value EBUSY unless the calling thread already holds the
 * lock, in which case it returns normally and increments the lock
 * count.  This method should normally only be called if a previous
 * call has been made to RecMutex::Lock::unlock() (that is, where the
 * thread owning the RecMutex::Lock object has temporarily allowed
 * another thread to take the mutex concerned if it is not recursively
 * locked).  It is not a cancellation point.  It does not throw.
 * @return 0 if successful, otherwise EBUSY or other pthread mutex
 * error number.
 * @note With this library implementation, apart from EBUSY, the only
 * other pthread error number which could be returned by this method
 * is EAGAIN, which it would do if the maximum recursive lock count
 * for the particular mutex in question has been reached.  Usually
 * this number is at or around INT_MAX and hence an EAGAIN error
 * return value is usually not worth checking for except during
 * debugging.
 * @sa RecMutex::TrackLock.
 */
 int trylock() noexcept {return mutex.trylock();}

/**
 * This calls RecMutex::unlock() and so unlocks a locked mutex owned
 * by the calling thread, so temporarily allowing another thread to
 * take the mutex if the mutex has not been recursively locked, or if
 * it has been recursively locked decrements the lock count, so
 * temporarily relinquishing ownership.  This method should normally
 * only be called if it is to be followed by a call to
 * RecMutex::Lock::lock() or a successful call to
 * RecMutex::Lock::trylock() before the RecMutex::Lock object
 * concerned goes out of scope (otherwise RecMutex::Lock's destructor
 * will attempt to decrement the lock count of a mutex which already
 * has a lock count of 0 or which another thread has by then taken
 * ownership or leave the lock count in an unbalanced condition -
 * RecMutex::Lock objects do not maintain state).  See
 * RecMutex::TrackLock::unlock() for a safe version of this method.
 * It is not a cancellation point.  It does not throw.
 * @return 0 if successful, otherwise the pthread mutex error number.  
 * @note With this library implementation, the only pthread error
 * number which could be returned by this method is EPERM because the
 * calling thread does not own the particular mutex in question
 * (however POSIX does not require that return value in that case and
 * hence the return value is usually not worth checking for except
 * during debugging).
 * @sa RecMutex::TrackLock.
 */
 int unlock() noexcept {return mutex.unlock();}

/**
 * This constructor locks the mutex passed to it.  See
 * RecMutex::lock() for a description of the outcomes.  It is not a
 * cancellation point.
 * @param mutex_ The mutex to be locked.
 * @exception Cgu::Thread::RecMutexError Throws this exception if
 * initialization of the mutex fails because the maximum recursive
 * lock count for the particular mutex in question has been reached.
 * Usually this number is at or around INT_MAX so it is not usually
 * useful to check for it except during debugging.
 */
  Lock(RecMutex& mutex_): mutex(mutex_) {if (mutex.lock()) throw RecMutexError();}

/**
 * This constructor takes an already locked mutex (say as a result of
 * RecMutex::trylock()), and takes ownership of it.  It is not a
 * cancellation point.  It does not throw.
 * @param mutex_ The mutex to be managed by this object.
 * @param tag Pass the Cgu::Thread::locked enum tag to this parameter.
 */
  Lock(RecMutex& mutex_, Locked tag) noexcept: mutex(mutex_) {}

/**
 * This class requires initialisation with a RecMutex.  The default
 * constructor is deleted.
 */
  Lock() = delete;

/**
 * The destructor unlocks the owned mutex.  See RecMutex::unlock() for
 * a description of the outcomes.  It is not a cancellation point.  It
 * does not throw.
 */
  ~Lock() {mutex.unlock();}

/* Only has effect if --with-glib-memory-slices-compat or
 * --with-glib-memory-slices-no-compat option picked */
  CGU_GLIB_MEMORY_SLICES_FUNCS
};

/**
 * @class RecMutex::TrackLock mutex.h c++-gtk-utils/mutex.h
 * @brief A scoped locking class for exception safe RecMutex locking
 * which tracks the status of its mutex.
 * @sa Thread::RecMutex Thread::RecMutex::Lock Thread::Thread
 *
 * This class is similar to a RecMutex::Lock object, except that it
 * tracks whether the mutex it manages is locked by the thread
 * creating the RecMutex::TrackLock object with respect to the
 * particular locking operation to be governed by the object (provided
 * that, while the RecMutex::TrackLock object exists, the thread
 * creating it only accesses the mutex with respect that particular
 * operation through that object).  This enables
 * RecMutex::TrackLock::unlock() to be used without it being followed
 * later by a call to RecMutex::TrackLock::lock() or a successful call
 * to RecMutex::TrackLock::trylock(), and also permits locking to be
 * deferred until after construction of the lock object.  Note that
 * only one thread may call the methods of any one RecMutex::TrackLock
 * object, including causing its destructor to be invoked.
 */

class RecMutex::TrackLock {
  RecMutex& mutex;
  bool owner;

public:

/**
 * This class cannot be copied.  The copy constructor is deleted.
 */
  TrackLock(const RecMutex::TrackLock&) = delete;

/**
 * This class cannot be copied.  The assignment operator is deleted.
 */
  RecMutex::TrackLock& operator=(const RecMutex::TrackLock&) = delete;

/**
 * This calls RecMutex::lock(), and so locks the mutex and acquires
 * ownership.  It blocks if the mutex is already locked until the
 * mutex becomes free, unless the calling thread already holds the
 * lock, in which case it increments the lock count and returns
 * immediately.  This method should normally only be called if a
 * previous call has been made to RecMutex::TrackLock::unlock() or
 * this RecMutex::TrackLock object has been constructed with the
 * Thread::defer enum tag.  It is not a cancellation point.  It does
 * not throw.
 * @return 0 if successful, otherwise the pthread mutex error number.
 * @note With this library implementation, the only pthread error
 * number which could be returned by this method is EAGAIN, which it
 * would do if the maximum recursive lock count for the particular
 * mutex in question has been reached.  Usually this number is at or
 * around INT_MAX and hence the return value is usually not worth
 * checking for except during debugging.
 */
  int lock() noexcept {int ret = mutex.lock(); if (!owner) owner = !ret; return ret;}

/**
 * This calls RecMutex::trylock(), and so tries to lock the mutex and
 * acquire ownership, but returns immediately if it is already locked
 * with value EBUSY unless the calling thread already holds the lock,
 * in which case it returns normally and increments the lock count.
 * This method should normally only be called if a previous call has
 * been made to RecMutex::TrackLock::unlock() or this
 * RecMutex::TrackLock object has been constructed with the
 * Thread::defer enum tag.  It is not a cancellation point.  It does
 * not throw.
 * @return 0 if successful, otherwise EBUSY or other pthread mutex
 * error number.
 * @note With this library implementation, apart from EBUSY, the only
 * other pthread error number which could be returned by this method
 * is EAGAIN, which it would do if the maximum recursive lock count
 * for the particular mutex in question has been reached.  Usually
 * this number is at or around INT_MAX and hence an EAGAIN error
 * return value is usually not worth checking for except during
 * debugging.
 */
  int trylock() noexcept {int ret = mutex.trylock(); if (!owner) owner = !ret; return ret;}

/**
 * This calls RecMutex::unlock(), and so unlocks a locked mutex owned
 * by the calling thread, or decrements the lock count (if it has been
 * recursively locked).  It will cause is_owner() to return false
 * unless a subsequent call is made to lock() or a subsequent
 * successful call is made to trylock().  It is not a cancellation
 * point.  It does not throw.
 * @return 0 if successful, otherwise the pthread mutex error number.
 * @note With this library implementation, the only pthread error
 * number which could be returned by this method is EPERM because the
 * calling thread does not own the particular mutex in question
 * (however POSIX does not require that return value in that case and
 * hence the return value is usually not worth checking for except
 * during debugging).
 */
  int unlock() noexcept {int ret = mutex.unlock(); if (owner) owner = ret; return ret;}

/**
 * Indicates whether the mutex managed by this Mutex::TrackLock object
 * is locked by it (whether originally or recursively) and so owned by
 * it.  It does not throw.
 * @return true if the mutex is owned by this object, otherwise false.
 */
  bool is_owner() const noexcept {return owner;}

/**
 * This constructor locks the mutex passed to it.  See
 * RecMutex::lock() for a description of the outcomes.  It is not a
 * cancellation point.  It does not throw.
 * @param mutex_ The mutex to be locked.
 * @exception Cgu::Thread::RecMutexError Throws this exception if
 * initialization of the mutex fails because the maximum recursive
 * lock count for the particular mutex in question has been reached.
 * Usually this number is at or around INT_MAX so it is not usually
 * useful to check for it except during debugging.
 */
  TrackLock(RecMutex& mutex_): mutex(mutex_), owner(true) {if (mutex.lock()) throw RecMutexError();}

/**
 * This constructor takes an already locked mutex (say as a result of
 * RecMutex::trylock()), and takes ownership of it.  It is not a
 * cancellation point.  It does not throw.
 * @param mutex_ The mutex to be managed by this object.
 * @param tag Pass the Cgu::Thread::locked enum tag to this parameter.
 */
  TrackLock(RecMutex& mutex_, Locked tag) noexcept: mutex(mutex_), owner(true) {}

/**
 * This constructor defers locking of the mutex (and so taking
 * ownership of it) until an explicit call to lock() or trylock() is
 * made.  It is not a cancellation point.  It does not throw.
 * @param mutex_ The mutex to be managed by this object.
 * @param tag Pass the Cgu::Thread::defer enum tag to this parameter.
 */
  TrackLock(RecMutex& mutex_, DeferLock tag) noexcept: mutex(mutex_), owner(false) {}

/**
 * This class requires initialisation with a RecMutex.  The default
 * constructor is deleted.
 */
  TrackLock() = delete;

/**
 * The destructor unlocks the managed mutex if it is owned by this
 * RecMutex::TrackLock object.  See RecMutex::unlock() for a
 * description of the outcomes if it is so owned.  It is not a
 * cancellation point.  It does not throw.
 */
  ~TrackLock() {if (owner) mutex.unlock();}

/* Only has effect if --with-glib-memory-slices-compat or
 * --with-glib-memory-slices-no-compat option picked */
  CGU_GLIB_MEMORY_SLICES_FUNCS
};

/**
 * @class GrecmutexLock mutex.h c++-gtk-utils/mutex.h
 * @brief A scoped locking class for exception safe locking of
 * GStaticRecMutex objects.
 * @details This class is mainly intended for use where the native
 * pthreads implementation does not support recursive mutexes so
 * Cgu::Thread::RecMutex and Cgu::Thread::RecMutex::Lock cannot be
 * used.
 *
 * It should be noted that this class is for use with GStaticRecMutex
 * objects, and not the GRecMutex objects available in glib from glib
 * version 2.32.  If glib >= 2.32 is installed, it can be assumed that
 * native recursive mutexes and so Cgu::Thread::RecMutex are
 * available, as glib >= 2.32 also uses native recursive mutexes.
 */
class GrecmutexLock {
  GStaticRecMutex& mutex;
public:

/**
 * This class cannot be copied.  The copy constructor is deleted.
 */
  GrecmutexLock(const GrecmutexLock&) = delete;

/**
 * This class cannot be copied.  The assignment operator is deleted.
 */
  GrecmutexLock& operator=(const GrecmutexLock&) = delete;

/**
 * This method provides access to the GStaticRecMutex object locked by
 * this GrecmutexLock object.  It does not throw.  It is thread safe.
 * @return A pointer to the GStaticRecMutex object.
 */
  GStaticRecMutex* get() const noexcept {return &mutex;}
/**
 * This constructor locks the mutex and acquires ownership, and blocks
 * if it is already locked until it becomes free, unless the
 * constructing thread already holds the lock, in which case it
 * increments the lock count and returns immediately.  It is not a
 * cancellation point.  It does not throw.
 * @param mutex_ The mutex to be locked.
 */
  // this is not inline, so we can apply GLIB_VERSION_MIN_REQUIRED
  GrecmutexLock(GStaticRecMutex& mutex_);

/**
 * This constructor takes an already locked mutex (say as a result of
 * g_static_rec_mutex_trylock()), and takes ownership of it.  It is not a
 * cancellation point.  It does not throw.
 * @param mutex_ The mutex to be managed by this object.
 * @param tag Pass the Cgu::Thread::locked enum tag to this parameter.
 */
  GrecmutexLock(GStaticRecMutex& mutex_, Locked tag): mutex(mutex_) {}

/**
 * This class requires initialisation with a GStaticRecMutex.  The
 * default constructor is deleted.
 */
  GrecmutexLock() = delete;

/**
 * The destructor unlocks the owned mutex, and either relinquishes
 * ownership (if the mutex has not been recursively locked) or
 * decrements the lock count (if it has).  It is not a cancellation
 * point.  It does not throw.
 */
  // this is not inline, so we can apply GLIB_VERSION_MIN_REQUIRED
  ~GrecmutexLock();

/* Only has effect if --with-glib-memory-slices-compat or
 * --with-glib-memory-slices-no-compat option picked */
  CGU_GLIB_MEMORY_SLICES_FUNCS
};

} // namespace Thread

} // namespace Cgu

#endif
