/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
    QC_RWLock.qpp

    Qore Programming Language

    Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    Note that the Qore library is released under a choice of three open-source
    licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
    information.
*/

#include <qore/Qore.h>
#include "qore/intern/QC_RWLock.h"
#include "qore/intern/QC_AbstractSmartLock.h"

//! The RWLock class implements a read-write thread lock
/** @par Overview
    This class inherits AbstractSmartLock, so it can be used by @ref Qore::Thread::Condition "Condition" objects, while using either the read lock or the write lock.\n\n
    The RWLock class implements a read-write lock for efficient thread locking when write actions must be atomic and reads can be made in parallel if no write is in progress. When a thread holds the write lock, no other thread can grab the read or write lock. Multiple threads can hold the read lock at one time.\n\n
    As with all %Qore threading primitives, this class supports deadlock detection and throws exceptions when threading errors are encountered (for example, trying to free the read lock while holding the write lock, etc).\n\n
    This read-write lock favors readers, so the read lock can be safely acquired recursively.\n\n
    See the @ref Qore::Thread::AutoReadLock "AutoReadLock" and the @ref Qore::Thread::AutoWriteLock "AutoWriteLock" classes for classes that assist in exception-safe RWLock locking.\n\n
    Additionally, the @ref on_exit "on_exit statement" can provide exception-safe RWLock handling at the lexical block level as in the following example:
    @code{.py}
{
    rwl.writeLock();
    on_exit
        rwl.writeUnlock();

    # ... when this block exits the lock will be released, even in the
    #     case of return statements or exceptions
}
    @endcode

    @par Thread Resource Handling
    The RWLock class manages the lock as a @ref thread_resources "thread resource"; if the lock is not released when the
    thread exits (or when @ref Qore::throw_thread_resource_exceptions() or
    @ref Qore::throw_thread_resource_exceptions_to_mark() is called), the any read or write locks held are
    released automatically and a
    \c LOCK-ERROR exception is thrown describing the situation.\n\n
    Being an builtin class, the RWLock class does not
    inherit @ref Qore::Thread::AbstractThreadResource "AbstractThreadResource" explicitly as a part of the exported API,
    and the internal @ref Qore::Thread::AbstractThreadResource::cleanup() "AbstractThreadResource::cleanup()" method
    cannot be overridden or suppressed.

    @note This class is not available with the @ref PO_NO_THREAD_CLASSES parse option.
 */
qclass RWLock [dom=THREAD_CLASS; arg=RWLock* rwl; ns=Qore::Thread; vparent=AbstractSmartLock];

//! Creates the RWLock object
/**
    @par Example:
    @code{.py}
RWLock rwl();
    @endcode
 */
RWLock::constructor() {
    self->setPrivate(CID_RWLOCK, new RWLock);
}

//! Destroys the RWLock object
/** Note that it is a programming error to delete this object while other threads are blocked on it; in this case an exception is thrown in the deleting thread, and in each thread blocked on this object when it is deleted.

    @par Example:
    @code{.py}
delete rwl;
    @endcode

    @throw LOCK-ERROR Object deleted while other threads blocked on it
 */
RWLock::destructor() {
    rwl->destructor(xsink);
    rwl->deref(xsink);
}

//! Creates a new RWLock object, not based on the original
/**
    @par Example:
    @code{.py}
RWLock new_rwl = rwl.copy();
    @endcode
 */
RWLock::copy() {
    self->setPrivate(CID_RWLOCK, new RWLock);
}

//! Acquires the read lock; blocks if the write lock is already acquired by another thread
/**
    @par Example:
    @code{.py}
rwl.readLock();
    @endcode

    @throw THREAD-DEADLOCK A deadlock was detected while trying to acquire the lock
    @throw LOCK-ERROR RWLock::readLock() called while already holding the write lock, object deleted in another thread, etc.
 */
nothing RWLock::readLock() {
    rwl->readLock(xsink);
}

//! Acquires the read lock with a timeout value; blocks if the write lock is already acquired by another thread
/** Returns 0 for success, non-zero for timeout; exceptions are thrown for other errors

    @param timeout_ms a @ref timeout_type "timeout" value to wait to acquire the read lock; integers are interpreted
    as milliseconds; @ref relative_dates "relative date/time values" are interpreted literally (with a resolution of
    milliseconds); negative timeouts mean wait indefinitely

    @return 0 for success, non-zero for timeout; exceptions are thrown for other errors

    @par Example:
    @code{.py}
if (rwl.readLock(1500ms))
    throw "TIMEOUT", "timed out after 1.5s waiting for the read lock";
    @endcode

    @throw THREAD-DEADLOCK A deadlock was detected while trying to acquire the lock
    @throw LOCK-ERROR RWLock::readLock() called while already holding the write lock, object deleted in another
    thread, etc.
*/
int RWLock::readLock(timeout timeout_ms) {
    return rwl->readLock(xsink, timeout_ms);
}

//! Decrements the read lock counter and releases the read lock if the counter is zero. If at least one thread is blocked trying to acquire the write lock and the read counter reaches zero, then one thread waiting on the write lock is woken up
/**
    @par Example:
    @code{.py}
rwl.readUnlock();
    @endcode

    @throw LOCK-ERROR RWLock::readUnlock() called while not holding the read lock, object deleted in another thread, etc
*/
nothing RWLock::readUnlock() {
    rwl->readUnlock(xsink);
}

//! Acquires the write lock with a timeout value; blocks if the read lock is already acquired by another thread
/** Returns 0 for success, non-zero for timeout; exceptions are thrown for other errors

    @param timeout_ms a @ref timeout_type "timeout" value to wait to acquire the write lock; integers are interpreted
    as milliseconds; @ref relative_dates "relative date/time values" are interpreted literally (with a resolution of
    milliseconds); negative timeouts mean wait indefinitely

    @return 0 for success, non-zero for timeout; exceptions are thrown for other errors

    @par Example:
    @code{.py}
if (rwl.writeLock(1500ms))
    throw "TIMEOUT", "timed out after 1.5s waiting for the write lock";
    @endcode

    @throw THREAD-DEADLOCK A deadlock was detected while trying to acquire the lock
    @throw LOCK-ERROR RWLock::writeLock() called while already holding the read lock, object deleted in another
    thread, etc.
*/
int RWLock::writeLock(timeout timeout_ms) {
    return rwl->grab(xsink, timeout_ms);
}

//! Acquires the write lock; blocks if the read lock is already acquired by another thread
/**
    @par Example:
    @code{.py}
rwl.writeLock();
    @endcode

    @throw THREAD-DEADLOCK A deadlock was detected while trying to acquire the lock
    @throw LOCK-ERROR RWLock::writeLock() called while already holding the read lock, object deleted in another thread, etc.
*/
nothing RWLock::writeLock() {
    rwl->grab(xsink);
}

//! Releases the write lock, if any readers are waiting, wakes up all readers, otherwise if any writers are waiting, then wakes one up
/**
    @par Example:
    @code{.py}
rwl.writeUnlock();
    @endcode

    @throw LOCK-ERROR RWLock::writeUnlock() called while not holding the write lock, object deleted in another thread, etc
*/
nothing RWLock::writeUnlock() {
    rwl->release(xsink);
}

//! Acquires the read lock only if it can be acquired immediately
/** @return 0 for success (read lock acquired, read lock count incremented) or -1 if the call would block (write lock owned by another thread) or an error occured

    @par Example:
    @code{.py}
if (!rwl.tryReadLock()) {
    on_exit rwl.readUnlock();
    do_something_in_read_lock();
}
    @endcode
*/
int RWLock::tryReadLock() {
    return rwl->tryReadLock();
}

//! Acquires the write lock only if it can be acquired immediately
/** @return 0 for success (write lock acquired) or -1 if the call would block (read lock owned by another thread) or an error occured
    @par Example:
    @code{.py}
if (!rwl.tryWriteLock()) {
    on_exit rwl.writeUnlock();
    do_something_in_write_lock();
}
    @endcode
*/
int RWLock::tryWriteLock() {
    return rwl->tryGrab();
}

//! Returns the read lock count
/**
    @return the read lock count

    @par Example:
    @code{.py}
int num = rwl.numReaders();
    @endcode
*/
int RWLock::numReaders() [flags=CONSTANT] {
    return rwl->numReaders();
}

//! Returns the number of threads waiting on the read lock
/** @return the number of threads waiting on the read lock

    @par Example:
    @code{.py}
int num = rwl.getReadWaiting();
    @endcode
*/
int RWLock::getReadWaiting() [flags=CONSTANT] {
    return rwl->getReadWaiting();
}

//! Returns the number of threads waiting on the write lock
/** @return the number of threads waiting on the write lock

    @par Example:
    @code{.py}
int num = rwl.getWriteWaiting();
    @endcode
*/
int RWLock::getWriteWaiting() [flags=CONSTANT] {
    return rwl->getWriteWaiting();
}

//! Returns @ref True if the current thread is holding either the read lock or the write lock, @ref False if not
/** @return @ref True if the current thread is holding either the read lock or the write lock, @ref False if not

    @par Example:
    @code{.py}
if (rwl.lockOwner())
    printf("TID %d has either the read lock or the write lock\n", q_gettid());
    @endcode
*/
bool RWLock::lockOwner() [flags=CONSTANT] {
    return rwl->lockOwner();
}

//! Returns @ref True if the current thread is holding the read lock, @ref False if not
/** @return @ref True if the current thread is holding the read lock, @ref False if not

    @par Example:
    @code{.py}
if (rwl.readLockOwner())
    printf("TID %d has the read lock\n", q_gettid());
    @endcode
 */
bool RWLock::readLockOwner() [flags=CONSTANT] {
    return rwl->readLockOwner();
}

//! Returns @ref True if the current thread is holding the write lock, @ref False if not
/** @return @ref True if the current thread is holding the write lock, @ref False if not

    @par Example:
    @code{.py}
if (rwl.writeLockOwner())
    printf("TID %d has the write lock\n", q_gettid());
    @endcode
 */
bool RWLock::writeLockOwner() [flags=CONSTANT] {
    return rwl->writeLockOwner();
}
