/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
  QC_AutoLock.qpp
 
  Qore Programming Language
 
  Copyright (C) 2003 - 2023 David Nichols
 
  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_AutoLock.h"
#include "qore/intern/QC_Mutex.h"

//! A helper class for the @ref Qore::Thread::Mutex "Mutex" class for exception-safe @ref Qore::Thread::Mutex "Mutex" handling
/** AutoLock objects, when used along with a @ref Qore::Thread::Mutex "Mutex" object, allow %Qore programmers to safely acquire and release a @ref Qore::Thread::Mutex "Mutex" lock, even if exceptions are thrown or @ref return "return statements" are executed in the block where the AutoLock object is created.

    AutoLock objects are helper objects that acquire a @ref Qore::Thread::Mutex "Mutex" for the lifetime of the object.

    For this reason, it is only appropriate to assign an AutoLock object to a local variable, so when the local variable goes out of scope, the AutoLock object will be deleted and the @ref Qore::Thread::Mutex "Mutex" will be automatically released.

    For example:
    @code{.py}
our Mutex mutex();

sub check_error(error) {
    # note that the Mutex is acquired in the AutoLock constructor, and
    # the Mutex will be released as soon as the block is exited below.
    # (with either the throw statement or the return statement)
    AutoLock al(mutex);
    if (error)
        throw "ERROR", "sorry, an error happened";

    return "OK";
}
    @endcode

    The destructor will call Mutex::unlock() only if the current thread owns the lock, so it is safe to unlock the lock manually (or by calling AutoLock::unlock()) while the AutoLock object is in scope.

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

//! Creates the AutoLock object based on the @ref Qore::Thread::Mutex "Mutex" argument passed and immediately calls Mutex::lock()
/** The AutoLock object immediately calls Mutex::lock() on the @ref Qore::Thread::Mutex "Mutex" object passed, and saves it; Mutex::unlock() is called in the destructor if the lock is still held by the current thread.
 
    @par Example:
    @code{.py}
{
    # when the block exits, the lock is automatically released
    AutoLock al(mutex);
}
    @endcode

    @param mutex a @ref Qore::Thread::Mutex "Mutex" object to lock immediately and hold for the scope of the AutoLock object (unless manually unlocked)

    @throw LOCK-ERROR lock called twice in the same thread, @ref Qore::Thread::Mutex "Mutex" object has already been deleted in another thread, etc
    @throw THREAD-DEADLOCK a deadlock was detected while trying to acquire the lock
 */
AutoLock::constructor(Mutex[SmartMutex] mutex) {
   QoreAutoLock *qsl = new QoreAutoLock(mutex, xsink);
   if (*xsink)
      qsl->deref(xsink);
   else
      self->setPrivate(CID_AUTOLOCK, qsl);
}

//! Calls @ref Qore::Thread::Mutex::unlock() "Mutex::unlock()" on the saved @ref Qore::Thread::Mutex "Mutex" object and destroys the AutoLock object
/** @ref Qore::Thread::Mutex::unlock() "Mutex::unlock()" is only called if the current thread owns the lock

    @par Example:
    @code{.py}
delete al;
    @endcode
 */
AutoLock::destructor() {
   al->destructor(xsink);
   al->deref(xsink);
}

//! Throws an exception; objects of this class cannot be copied
/** 
    @throw AUTOLOCK-COPY-ERROR Objects of this class cannot be copied
 */
AutoLock::copy() {
   xsink->raiseException("AUTOLOCK-COPY-ERROR", "objects of this class cannot be copied");
}

//! Attempts to relock the @ref Qore::Thread::Mutex "Mutex" object being managed
/** Do not call this method unless the @ref Qore::Thread::Mutex "Mutex" object being managed has been unlocked since the constructor

    @par Example:
    @code{.py}
al.lock();
    @endcode

    @throw LOCK-ERROR lock called twice in the same thread, @ref Qore::Thread::Mutex "Mutex" object has already been deleted in another thread, etc
    @throw THREAD-DEADLOCK a deadlock was detected while trying to acquire the lock
 */
nothing AutoLock::lock() {
   al->lock(xsink);
}

//! Attempts to relock the @ref Qore::Thread::Mutex "Mutex" object being managed; acquires the lock only if it is not already held; returns 0 for success (lock acquired) or -1 if the call would block
/** 
    @return 0 for success (lock acquired) or -1 if the call would block (lock not acquired because it's held by another thread)

    @par Example:
    @code{.py}
int i = al.trylock();
    @endcode

    @throw LOCK-ERROR object deleted in another thread, etc
    @throw THREAD-DEADLOCK a deadlock was detected while trying to acquire the lock
*/
int AutoLock::trylock() {
   return al->trylock(); 
}

//! Unlocks the @ref Qore::Thread::Mutex "Mutex" object being managed; wakes up one thread if any threads are blocked on this lock
/** 
    @par Example:
    @code{.py}
al.unlock();
    @endcode

    @throw LOCK-ERROR unlock called by a thread that does not own the lock or the lock is not locked, object deleted in another thread, etc
*/
nothing AutoLock::unlock() {
   al->unlock(xsink);
}
