/* Copyright (C) 2010 to 2014 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_RW_LOCK_H
#define CGU_RW_LOCK_H

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

#include <c++-gtk-utils/mutex.h> // for Locked and DeferLock enumerations
#include <c++-gtk-utils/cgu_config.h>

/**
 * @file rw_lock.h
 * @brief Provides wrapper class for pthread read-write locks, and
 * scoped locking classes for exception safe locking of read-write
 * locks.
 */

namespace Cgu {

namespace Thread {

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

/**
 * @class RWLock rw_lock.h c++-gtk-utils/rw_lock.h
 * @brief A wrapper class for pthread read-write locks.
 * @sa Thread::Thread Thread::RWLock::ReaderLock Thread::RWLock::ReaderTrackLock Thread::RWLock::WriterLock Thread::RWLock::WriterTrackLock 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
 * in practice be used interchangeably with those started by C++11/14
 * std::thread, as in C++11/14 on unix-like OSes these facilities will
 * be built on top of pthreads (for which purpose C++11/14 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.
 *
 * RWLock 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::RWLockError thrown by its constructor, but if a static
 * global read-write lock throws there is nothing that could be done
 * anyway except abort, and it would show that the pthreads
 * installation is seriously defective.)
 *
 * Read-write locks are similar to mutexes except that they allow more
 * than one thread to hold the lock for reading at once.  This can
 * offer advantages over a mutex where a particular shared object is
 * thread safe for lock-free reading by multiple threads
 * simultaneously, is frequently read by different threads and is not
 * often modified.  However, the implementation of a read-write lock
 * is more complex than that of a mutex, and unless the particular
 * pthread read-write lock scheduling implementation favours
 * already-blocking writers over later readers whenever a read-write
 * lock object is unlocked, writer starvation can occur.  Unless all
 * the reads are of significant duration, might cause (if protected by
 * a mutex) significant contention between each other and greatly
 * exceed the number of times the write lock is held, then it is
 * usually better to use an ordinary mutex.
 */

class RWLock {
  pthread_rwlock_t pthr_rwlock;
  
public:
  class ReaderLock;
  class ReaderTrackLock;
  class WriterLock;
  class WriterTrackLock;

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

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

/**
 * Locks the read-write lock for reading.  Blocks if already locked
 * for writing until it becomes free.  More than one thread may
 * simultaneously hold a read lock, and a thread may lock for reading
 * recursively provided that each call to this method is matched by a
 * call to unlock().  It is not a cancellation point.  It does not
 * throw.  It is thread safe.
 * @return 0 if successful, otherwise the pthread read-write lock
 * error number.
 * @note With this library implementation, the only pthread error
 * numbers which could be returned by this method are EDEADLK and
 * EAGAIN.  EDEADLK would be returned if the default pthread reader
 * lock behaviour happens to return that error rather than deadlock
 * where the thread calling this method already holds a write lock on
 * this read-write lock.  Most default implementations do not do this
 * (they just deadlock) and hence the return value is usually not
 * worth checking for except during debugging.  EAGAIN would be
 * returned if the maximum number of read locks for this read-write
 * lock has been reached.  Usually this number is at or around INT_MAX
 * so it is also not usually useful to check for it except during
 * debugging.
 */
  int reader_lock() noexcept {return pthread_rwlock_rdlock(&pthr_rwlock);}

/**
 * Tries to lock the read-write lock for reading, but returns
 * immediately with value EBUSY if it is already locked for writing.
 * More than one thread may simultaneously hold a read lock, and a
 * thread may lock for reading recursively provided that each
 * successful call to this method is matched by a call to unlock().
 * It is not a cancellation point.  It does not throw.  It is thread
 * safe.
 * @return 0 if successful, otherwise EBUSY or other pthread
 * read-write lock 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 would be returned if the maximum number of read
 * locks for this read-write lock 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.
 */
  int reader_trylock() noexcept {return pthread_rwlock_tryrdlock(&pthr_rwlock);}

/**
 * Locks the read-write lock for writing and acquires ownership.
 * Blocks if already locked for reading or writing 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 read-write lock
 * 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 reader lock behaviour happens to
 * return that error rather than deadlock where the thread calling
 * this method already holds a read lock or write lock on this
 * read-write lock.  Most default implementations do not do this (they
 * just deadlock) and hence the return value is usually not worth
 * checking for except during debugging.
 */
  int writer_lock() noexcept {return pthread_rwlock_wrlock(&pthr_rwlock);}

/**
 * Tries to lock the read-write lock for writing and acquire
 * ownership, but returns immediately with value EBUSY if it is
 * already locked for reading or writing.  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 writer_trylock() noexcept {return pthread_rwlock_trywrlock(&pthr_rwlock);}

/**
 * Unlocks a read-write lock previously locked for reading or writing
 * by the calling thread.  If the calling thread has locked the
 * read-write lock for writing, it relinquishes ownership.  If it has
 * previously locked the read-write lock for reading, it releases that
 * particular lock, but the read-write lock may remain locked for
 * reading if it has been locked for reading recursively or other
 * threads hold a read lock and the particular implementation does not
 * provide writer priority.  It is not a cancellation point.  It does
 * not throw.
 * @return 0 if successful, otherwise the pthread read-write lock
 * 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 hold a lock on this read-write lock (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_rwlock_unlock(&pthr_rwlock);}

/**
 * Initialises the pthread read-write lock.  It is not a cancellation
 * point.
 * @exception Cgu::Thread::RWLockError Throws this exception if
 * initialisation of the read-write lock 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 read-write
 * locks.)
 */
  RWLock() {if (pthread_rwlock_init(&pthr_rwlock, 0)) throw RWLockError();}

/**
 * Destroys the pthread read-write lock.  It is not a cancellation
 * point.  It does not throw.
 */
  ~RWLock() {pthread_rwlock_destroy(&pthr_rwlock);}

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

/**
 * @class RWLock::ReaderLock rw_lock.h c++-gtk-utils/rw_lock.h
 * @brief A scoped locking class for exception safe RWLock read locking.
 * @sa Thread::RWLock Thread::RWLock::ReaderTrackLock Thread::RWLock::WriterLock Thread::RWLock::WriterTrackLock Thread::Thread
 */

class RWLock::ReaderLock {
  RWLock& rw_lock;

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

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

/**
 * Calls RWLock::reader_lock(), and so relocks the read-write lock for
 * reading.  It blocks if the read-write lock is already locked for
 * writing until it becomes free.  This method should normally only be
 * called if a previous call has been made to
 * RWLock::ReaderLock::unlock() (that is, where the thread owning the
 * RWLock::ReaderLock object has temporarily allowed another thread to
 * take the read-write lock concerned for writing if another thread
 * does not hold a read lock or the read-write lock has not been
 * recursively locked for reading).  It is not a cancellation point.
 * It does not throw.
 * @return 0 if successful, otherwise the pthread read-write lock
 * error number.
 * @note With this library implementation, the only pthread error
 * numbers which could be returned by this method are EDEADLK and
 * EAGAIN.  EDEADLK would be returned if the default pthread reader
 * lock behaviour happens to return that error rather than deadlock
 * where the thread calling this method already holds a write lock on
 * the particular read-write lock in question.  Most default
 * implementations do not do this (they just deadlock) and hence the
 * return value is usually not worth checking for except during
 * debugging.  EAGAIN would be returned if the maximum number of read
 * locks for the read-write lock in question has been reached.
 * Usually this number is at or around INT_MAX so it is also not
 * usually useful to check for it except during debugging.
 * @sa RWLock::ReaderTrackLock
 */
  int lock() noexcept {return rw_lock.reader_lock();}

/**
 * Calls RWLock::reader_trylock(), and so tries to relock the
 * read-write lock for reading, but returns immediately with value
 * EBUSY if it is already locked for writing.  This method should
 * normally only be called if a previous call has been made to
 * RWLock::ReaderLock::unlock() (that is, where the thread owning the
 * RWLock::ReaderLock object has temporarily allowed another thread to
 * take the read-write lock concerned for writing if another thread
 * does not hold a read lock or the read-write lock has not been
 * recursively locked for reading).  It is not a cancellation point.
 * It does not throw.
 * @return 0 if successful, otherwise EBUSY or other pthread
 * read-write lock 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 would be returned if the maximum number of read
 * locks for the particular read-write lock 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.
 * @sa RWLock::ReaderTrackLock
 */
  int trylock() noexcept {return rw_lock.reader_trylock();}

/**
 * Calls RWLock::unlock(), and so unlocks a locked read-write lock
 * held by the calling thread for reading (so temporarily allowing
 * another thread to take the read-write lock for writing should no
 * other read lock be held or the particular implementation provides
 * writer priority).  This method should normally only be called if it
 * is to be followed by a call to RWLock::ReaderLock::lock() or a
 * successful call to RWLock::ReaderLock::trylock() before the
 * RWLock::ReaderLock object concerned goes out of scope (otherwise
 * RWLock::ReaderLock's destructor will attempt to unlock an already
 * unlocked read-write lock or a read-write lock of which another
 * thread holds a lock - RWLock::ReaderLock objects do not maintain
 * state).  See RWLock::ReaderTrackLock::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 read-write lock
 * 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 hold a lock on the particular read-write lock
 * 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 RWLock::ReaderTrackLock
 */
  int unlock() noexcept {return rw_lock.unlock();}

/**
 * This constructor locks for reading the read-write lock passed to
 * it.  It is not a cancellation point.
 * @param rw_lock_ The read-write lock to be locked for reading.
 * @exception Cgu::Thread::RWLockError Throws this exception if
 * initialization of the read-write lock fails because the maximum
 * number of read locks for the particular read-write lock in question
 * has been reached.  Usually this number is at or around INT_MAX so
 * it is not usually useful to check for the exception except during
 * debugging.  This exception may also be thrown if the thread
 * constructing this object already holds a write lock on the
 * read-write lock in question. It will do this if the default pthread
 * implementation returns EDEADLK in such a case instead of
 * deadlocking.  However as most default implementations will simply
 * deadlock in such circumstances, it is usually not worth checking
 * for this either except during debugging.
 */
  ReaderLock(RWLock& rw_lock_): rw_lock(rw_lock_) {if (rw_lock.reader_lock()) throw RWLockError();}

/**
 * This constructor takes a read-write lock already locked for reading
 * (say as a result of RWLock::reader_trylock()), and takes management
 * of that read lock operation.  It is not a cancellation point.  It
 * does not throw.
 * @param rw_lock_ The read-write lock to be managed for reading by
 * this object.
 * @param tag Pass the Cgu::Thread::locked enum tag to this parameter.
 */
  ReaderLock(RWLock& rw_lock_, Locked tag) noexcept: rw_lock(rw_lock_) {}

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

/**
 * The destructor unlocks the read-write lock which is managed for
 * reading.  It is not a cancellation point.  It does not throw.
 */
  ~ReaderLock() {rw_lock.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 RWLock::ReaderTrackLock rw_lock.h c++-gtk-utils/rw_lock.h
 * @brief A scoped locking class for exception safe RWLock read
 * locking which tracks the status of its read-write lock.
 * @sa Thread::RWLock Thread::RWLock::ReaderLock Thread::RWLock::WriterLock Thread::RWLock::WriterTrackLock Thread::Thread
 *
 * This class is similar to a RWLock::ReaderLock object, except that
 * it tracks whether the read-write lock it manages is locked for
 * reading by the thread creating the RWLock::ReaderTrackLock object
 * with respect to the particular read-locking operation to be
 * governed by the object (provided that, while the
 * RWLock::ReaderTrackLock object exists, the thread creating it only
 * accesses the managed read-write lock with respect that particular
 * operation through that object).  This enables
 * RWLock::ReaderTrackLock::unlock() to be used without it being
 * followed later by a call to RWLock::ReaderTrackLock::lock() or a
 * successful call to RWLock::ReaderTrackLock::trylock(), and also
 * permits locking to be deferred until after construction of the
 * RWLock::ReaderTrackLock object.  Note that only one thread may call
 * the methods of any one RWLock::ReaderTrackLock object, including
 * causing its destructor to be invoked.
 */

class RWLock::ReaderTrackLock {
  RWLock& rw_lock;
  bool owner;

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

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

/**
 * This calls RWLock::reader_lock(), and so locks the read-write lock
 * for reading and acquires ownership (which may be shared with other
 * read locks).  It blocks if the read-write lock is already locked
 * for writing until it becomes free.  This method should normally
 * only be called if a previous call has been made to
 * RWLock::ReaderTrackLock::unlock() or this RWLock::ReaderTrackLock
 * 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 read-write lock
 * error number.
 * @note With this library implementation, the only pthread error
 * numbers which could be returned by this method are EDEADLK and
 * EAGAIN.  EDEADLK would be returned if the default pthread reader
 * lock behaviour happens to return that error rather than deadlock
 * where the thread calling this method already holds a write lock on
 * the particular read-write lock in question.  Most default
 * implementations do not do this (they just deadlock) and hence the
 * return value is usually not worth checking for except during
 * debugging.  EAGAIN would be returned if the maximum number of read
 * locks for the read-write lock in question has been reached.
 * Usually this number is at or around INT_MAX so it is also not
 * usually useful to check for it except during debugging.
 */
  int lock() noexcept {int ret = rw_lock.reader_lock(); if (!owner) owner = !ret; return ret;}

/**
 * This calls RWLock::reader_trylock(), and so tries to lock the
 * read-write lock for reading and acquire ownership (which may be
 * shared with other read locks), but returns immediately with value
 * EBUSY if it is already locked for writing.  This method should
 * normally only be called if a previous call has been made to
 * RWLock::ReaderTrackLock::unlock() or this RWLock::ReaderTrackLock
 * 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
 * read-write lock 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 would be returned if the maximum number of read
 * locks for the particular read-write lock 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.
 */
  int trylock() noexcept {int ret = rw_lock.reader_trylock(); if (!owner) owner = !ret; return ret;}

/**
 * This calls RWLock::unlock(), and so unlocks a locked read-write
 * lock held by the calling thread for reading and relinquishes
 * ownership (whether it was sole or shared with other read locks).
 * 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 read-write lock
 * 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 hold a lock on the particular read-write lock
 * 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 = rw_lock.unlock(); if (owner) owner = ret; return ret;}

/**
 * Indicates whether the read-write lock managed by this
 * RWLock::ReaderTrackLock object is locked for reading by it and so
 * owned by it (whether solely or with other read locks).  It does not
 * throw.
 * @return true if the read-write lock is owned by this object,
 * otherwise false.
 */
  bool is_owner() const noexcept {return owner;}

/**
 * This constructor locks for reading the read-write lock passed to
 * it.  It is not a cancellation point.
 * @param rw_lock_ The read-write lock to be locked for reading.
 * @exception Cgu::Thread::RWLockError Throws this exception if
 * initialization of the read-write lock fails because the maximum
 * number of read locks for the particular read-write lock in question
 * has been reached.  Usually this number is at or around INT_MAX so
 * it is not usually useful to check for the exception except during
 * debugging.  This exception may also be thrown if the thread
 * constructing this object already holds a write lock on the
 * read-write lock in question. It will do this if the default pthread
 * implementation returns EDEADLK in such a case instead of
 * deadlocking.  However as most default implementations will simply
 * deadlock in such circumstances, it is usually not worth checking
 * for this either except during debugging.
 */
  ReaderTrackLock(RWLock& rw_lock_): rw_lock(rw_lock_), owner(true) {if (rw_lock.reader_lock()) throw RWLockError();}

/**
 * This constructor takes a read-write lock already locked for reading
 * (say as a result of RWLock::reader_trylock()), and takes management
 * of that read lock operation.  It is not a cancellation point.  It
 * does not throw.
 * @param rw_lock_ The read-write lock to be managed for reading by
 * this object.
 * @param tag Pass the Cgu::Thread::locked enum tag to this parameter.
 */
  ReaderTrackLock(RWLock& rw_lock_, Locked tag) noexcept: rw_lock(rw_lock_), owner(true) {}

/**
 * This constructor defers locking of the read-write lock for reading
 * until an explicit call to lock() or trylock() is made.  It is not a
 * cancellation point.  It does not throw.
 * @param rw_lock_ The read-write lock to be managed for reading by
 * this object.
 * @param tag Pass the Cgu::Thread::defer enum tag to this parameter.
 */
  ReaderTrackLock(RWLock& rw_lock_, DeferLock tag) noexcept: rw_lock(rw_lock_), owner(false) {}

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

/**
 * The destructor unlocks the read-write lock which is managed for
 * reading if it is owned by this RWLock::ReaderTrackLock object
 * (whether solely or with other read locks).  It is not a
 * cancellation point.  It does not throw.
 */
  ~ReaderTrackLock() {if (owner) rw_lock.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 RWLock::WriterLock rw_lock.h c++-gtk-utils/rw_lock.h
 * @brief A scoped locking class for exception safe RWLock write locking.
 * @sa Thread::RWLock Thread::RWLock::WriterTrackLock Thread::RWLock::ReaderLock Thread::RWLock::ReaderTrackLock Thread::Thread
 */

class RWLock::WriterLock {
  RWLock& rw_lock;

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

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

/**
 * Calls RWLock::writer_lock(), and so locks the read-write lock for
 * writing and reacquires ownership.  It blocks if the read-write lock
 * is already locked for reading or writing until it becomes free.
 * This method should normally only be called if a previous call has
 * been made to RWLock::WriterLock::unlock() (that is, where the
 * thread owning the RWLock::WriterLock object has temporarily allowed
 * another thread to take the read-write lock concerned for reading or
 * writing).  It is not a cancellation point.  It does not throw.
 * @return 0 if successful, otherwise the pthread read-write lock
 * 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 reader lock behaviour happens to
 * return that error rather than deadlock where the thread calling
 * this method already holds a read lock or write lock on the
 * particular read-write lock in question.  Most default
 * implementations do not do this (they just deadlock) and hence the
 * return value is usually not worth checking for except during
 * debugging.
 * @sa RWLock::WriterTrackLock
 */
  int lock() noexcept {return rw_lock.writer_lock();}

/**
 * Calls RWLock::writer_trylock(), and so tries to lock the read-write
 * lock for writing and reacquire ownership, but returns immediately
 * with value EBUSY if it is already locked for reading or writing.
 * This method should normally only be called if a previous call has
 * been made to RWLock::WriterLock::unlock() (that is, where the
 * thread owning the RWLock::WriterLock object has temporarily allowed
 * another thread to take the read-write lock concerned for reading or
 * writing).  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 RWLock::WriterTrackLock
 */
  int trylock() noexcept {return rw_lock.writer_trylock();}

/**
 * Calls RWLock::unlock(), and so unlocks a locked read-write lock
 * owned by the calling thread for writing and relinquishes ownership
 * (so temporarily allowing another thread to take the read-write
 * lock).  This method should normally only be called if it is to be
 * followed by a call to RWLock::WriterLock::lock() or a successful
 * call to RWLock::WriterLock::trylock() before the RWLock::WriterLock
 * object concerned goes out of scope (otherwise RWLock::WriterLock's
 * destructor will attempt to unlock an already unlocked read-write
 * lock or a read-write lock of which another thread has by then taken
 * ownership - RWLock::WriterLock objects do not maintain state).  See
 * RWLock::WriterTrackLock::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 read-write lock
 * 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 hold a lock on the particular read-write lock
 * 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 RWLock::WriterTrackLock
 */
  int unlock() noexcept {return rw_lock.unlock();}

/**
 * This constructor locks for writing the read-write lock passed to
 * it.  It is not a cancellation point.  It does not throw.
 * @param rw_lock_ The read-write lock to be locked for writing.
 */
  WriterLock(RWLock& rw_lock_) noexcept: rw_lock(rw_lock_) {rw_lock.writer_lock();}

/**
 * This constructor takes a read-write lock already locked for writing
 * (say as a result of RWLock::writer_trylock()), and takes ownership
 * of it.  It is not a cancellation point.  It does not throw.
 * @param rw_lock_ The read-write lock to be managed for writing by
 * this object.
 * @param tag Pass the Cgu::Thread::locked enum tag to this parameter.
 */
  WriterLock(RWLock& rw_lock_, Locked tag) noexcept: rw_lock(rw_lock_) {}

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

/**
 * The destructor unlocks the owned read-write lock.  It is not a
 * cancellation point.  It does not throw.
 */
  ~WriterLock() {rw_lock.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 RWLock::WriterTrackLock rw_lock.h c++-gtk-utils/rw_lock.h
 * @brief A scoped locking class for exception safe RWLock write
 * locking which tracks the status of its read-write lock..
 * @sa Thread::RWLock Thread::RWLock::WriterLock Thread::RWLock::ReaderLock Thread::RWLock::ReaderTrackLock Thread::Thread
 *
 * This class is similar to a RWLock::WriterLock object, except that
 * it tracks whether the read-write lock it manages is locked for
 * writing by the thread creating the RWLock::WriterTrackLock object
 * (provided that, while the RWLock::WriterTrackLock object exists,
 * the thread creating it only accesses the managed read-write lock
 * for write-locking through that object).  This enables
 * RWLock::WriterTrackLock::unlock() to be used without it being
 * followed later by a call to RWLock::WriterTrackLock::lock() or a
 * successful call to RWLock::WriterTrackLock::trylock(), and also
 * permits locking to be deferred until after construction of the
 * RWLock::WriterTrackLock object.  Note that only one thread may call
 * the methods of any one RWLock::WriterTrackLock object, including
 * causing its destructor to be invoked.
 */

class RWLock::WriterTrackLock {
  RWLock& rw_lock;
  bool owner;

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

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

/**
 * Calls RWLock::writer_lock(), and so locks the read-write lock for
 * writing and acquires ownership.  It blocks if the read-write lock
 * is already locked for reading or writing until it becomes free.
 * This method should normally only be called if a previous call has
 * been made to RWLock::WriterTrackLock::unlock() or this
 * RWLock::WriterTrackLock 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 read-write lock
 * 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 reader lock behaviour happens to
 * return that error rather than deadlock where the thread calling
 * this method already holds a read lock or write lock on the
 * particular read-write lock in question.  Most default
 * implementations do not do this (they just deadlock) and hence the
 * return value is usually not worth checking for except during
 * debugging.
 */
  int lock() noexcept {int ret = rw_lock.writer_lock(); if (!owner) owner = !ret; return ret;}

/**
 * Calls RWLock::writer_trylock(), and so tries to lock the read-write
 * lock for writing and acquire ownership, but returns immediately
 * with value EBUSY if it is already locked for reading or writing.
 * This method should normally only be called if a previous call has
 * been made to RWLock::WriterTrackLock::unlock() or this
 * RWLock::WriterTrackLock 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 = rw_lock.writer_trylock(); if (!owner) owner = !ret; return ret;}

/**
 * Calls RWLock::unlock(), and so unlocks a locked read-write lock
 * owned by the calling thread for writing and relinquishes ownership.
 * 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 read-write lock
 * 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 hold a lock on the particular read-write lock
 * 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 = rw_lock.unlock(); if (owner) owner = ret; return ret;}

/**
 * Indicates whether the read-write lock managed by this
 * RWLock::ReaderTrackLock object is locked for writing by it and so
 * owned by it.  It does not throw.
 * @return true if the read-write lock is owned by this object,
 * otherwise false.
 */
  bool is_owner() const noexcept {return owner;}

/**
 * This constructor locks for writing the read-write lock passed to
 * it.  It is not a cancellation point.  It does not throw.
 * @param rw_lock_ The read-write lock to be locked for writing.
 */
  WriterTrackLock(RWLock& rw_lock_) noexcept: rw_lock(rw_lock_), owner(true) {rw_lock.writer_lock();}

/**
 * This constructor takes a read-write lock already locked for writing
 * (say as a result of RWLock::writer_trylock()), and takes ownership
 * of it.  It is not a cancellation point.  It does not throw.
 * @param rw_lock_ The read-write lock to be managed for writing by
 * this object.
 * @param tag Pass the Cgu::Thread::locked enum tag to this parameter.
 */
  WriterTrackLock(RWLock& rw_lock_, Locked tag) noexcept: rw_lock(rw_lock_), owner(true) {}

/**
 * This constructor defers locking of the read-write lock for writing
 * until an explicit call to lock() or trylock() is made.  It is not a
 * cancellation point.  It does not throw.
 * @param rw_lock_ The read-write lock to be managed for writing by
 * this object.
 * @param tag Pass the Cgu::Thread::defer enum tag to this parameter.
 */
  WriterTrackLock(RWLock& rw_lock_, DeferLock tag) noexcept: rw_lock(rw_lock_), owner(false) {}

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

/**
 * The destructor unlocks the read-write lock which is managed for
 * writing if it is owned by this RWLock::WriterTrackLock object.  It
 * is not a cancellation point.  It does not throw.
 */
  ~WriterTrackLock() {if (owner) rw_lock.unlock();}

/* 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
