/**
 * The mutex module provides a primitive for maintaining mutually exclusive
 * access.
 *
 * Copyright: Copyright Sean Kelly 2005 - 2009.
 * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
 * Authors:   Sean Kelly
 * Source:    $(DRUNTIMESRC core/sync/_mutex.d)
 */

/*          Copyright Sean Kelly 2005 - 2009.
 * Distributed under the Boost Software License, Version 1.0.
 *    (See accompanying file LICENSE or copy at
 *          http://www.boost.org/LICENSE_1_0.txt)
 */
module core.sync.mutex;


public import core.sync.exception;

version (Windows)
{
    private import core.sys.windows.winbase /+: CRITICAL_SECTION, DeleteCriticalSection,
        EnterCriticalSection, InitializeCriticalSection, LeaveCriticalSection,
        TryEnterCriticalSection+/;
}
else version (Posix)
{
    private import core.sys.posix.pthread;
}
else
{
    static assert(false, "Platform not supported");
}

////////////////////////////////////////////////////////////////////////////////
// Mutex
//
// void lock();
// void unlock();
// bool tryLock();
////////////////////////////////////////////////////////////////////////////////


/**
 * This class represents a general purpose, recursive mutex.
 *
 * Implemented using `pthread_mutex` on Posix and `CRITICAL_SECTION`
 * on Windows.
 */
class Mutex :
    Object.Monitor
{
    ////////////////////////////////////////////////////////////////////////////
    // Initialization
    ////////////////////////////////////////////////////////////////////////////


    /**
     * Initializes a mutex object.
     *
     */
    this() @trusted nothrow @nogc
    {
        this(true);
    }

    /// ditto
    this() shared @trusted nothrow @nogc
    {
        this(true);
    }

    // Undocumented, useful only in Mutex.this().
    private this(this Q)(bool _unused_) @trusted nothrow @nogc
        if (is(Q == Mutex) || is(Q == shared Mutex))
    {
        version (Windows)
        {
            InitializeCriticalSection(cast(CRITICAL_SECTION*) &m_hndl);
        }
        else version (Posix)
        {
            import core.internal.abort : abort;
            pthread_mutexattr_t attr = void;

            !pthread_mutexattr_init(&attr) ||
                abort("Error: pthread_mutexattr_init failed.");

            scope (exit) !pthread_mutexattr_destroy(&attr) ||
                abort("Error: pthread_mutexattr_destroy failed.");

            !pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) ||
                abort("Error: pthread_mutexattr_settype failed.");

            !pthread_mutex_init(cast(pthread_mutex_t*) &m_hndl, &attr) ||
                abort("Error: pthread_mutex_init failed.");
        }

        m_proxy.link = this;
        this.__monitor = cast(void*) &m_proxy;
    }


    /**
     * Initializes a mutex object and sets it as the monitor for `obj`.
     *
     * In:
     *  `obj` must not already have a monitor.
     */
    this(Object obj) @trusted nothrow @nogc
    {
        this(obj, true);
    }

    /// ditto
    this(Object obj) shared @trusted nothrow @nogc
    {
        this(obj, true);
    }

    // Undocumented, useful only in Mutex.this(Object).
    private this(this Q)(Object obj, bool _unused_) @trusted nothrow @nogc
        if (is(Q == Mutex) || is(Q == shared Mutex))
    in
    {
        assert(obj !is null,
            "The provided object must not be null.");
        assert(obj.__monitor is null,
            "The provided object has a monitor already set!");
    }
    do
    {
        this();
        obj.__monitor = cast(void*) &m_proxy;
    }


    ~this() @trusted nothrow @nogc
    {
        version (Windows)
        {
            DeleteCriticalSection(&m_hndl);
        }
        else version (Posix)
        {
            import core.internal.abort : abort;
            !pthread_mutex_destroy(&m_hndl) ||
                abort("Error: pthread_mutex_destroy failed.");
        }
        this.__monitor = null;
    }


    ////////////////////////////////////////////////////////////////////////////
    // General Actions
    ////////////////////////////////////////////////////////////////////////////


    /**
     * If this lock is not already held by the caller, the lock is acquired,
     * then the internal counter is incremented by one.
     *
     * Note:
     *    `Mutex.lock` does not throw, but a class derived from Mutex can throw.
     *    Use `lock_nothrow` in `nothrow @nogc` code.
     */
    @trusted void lock()
    {
        lock_nothrow();
    }

    /// ditto
    @trusted void lock() shared
    {
        lock_nothrow();
    }

    /// ditto
    final void lock_nothrow(this Q)() nothrow @trusted @nogc
        if (is(Q == Mutex) || is(Q == shared Mutex))
    {
        version (Windows)
        {
            EnterCriticalSection(&m_hndl);
        }
        else version (Posix)
        {
            if (pthread_mutex_lock(&m_hndl) == 0)
                return;

            SyncError syncErr = cast(SyncError) cast(void*) typeid(SyncError).initializer;
            syncErr.msg = "Unable to lock mutex.";
            throw syncErr;
        }
    }

    /**
     * Decrements the internal lock count by one.  If this brings the count to
     * zero, the lock is released.
     *
     * Note:
     *    `Mutex.unlock` does not throw, but a class derived from Mutex can throw.
     *    Use `unlock_nothrow` in `nothrow @nogc` code.
     */
    @trusted void unlock()
    {
        unlock_nothrow();
    }

    /// ditto
    @trusted void unlock() shared
    {
        unlock_nothrow();
    }

    /// ditto
    final void unlock_nothrow(this Q)() nothrow @trusted @nogc
        if (is(Q == Mutex) || is(Q == shared Mutex))
    {
        version (Windows)
        {
            LeaveCriticalSection(&m_hndl);
        }
        else version (Posix)
        {
            if (pthread_mutex_unlock(&m_hndl) == 0)
                return;

            SyncError syncErr = cast(SyncError) cast(void*) typeid(SyncError).initializer;
            syncErr.msg = "Unable to unlock mutex.";
            throw syncErr;
        }
    }

    /**
     * If the lock is held by another caller, the method returns.  Otherwise,
     * the lock is acquired if it is not already held, and then the internal
     * counter is incremented by one.
     *
     * Returns:
     *  true if the lock was acquired and false if not.
     *
     * Note:
     *    `Mutex.tryLock` does not throw, but a class derived from Mutex can throw.
     *    Use `tryLock_nothrow` in `nothrow @nogc` code.
     */
    bool tryLock() @trusted
    {
        return tryLock_nothrow();
    }

    /// ditto
    bool tryLock() shared @trusted
    {
        return tryLock_nothrow();
    }

    /// ditto
    final bool tryLock_nothrow(this Q)() nothrow @trusted @nogc
        if (is(Q == Mutex) || is(Q == shared Mutex))
    {
        version (Windows)
        {
            return TryEnterCriticalSection(&m_hndl) != 0;
        }
        else version (Posix)
        {
            return pthread_mutex_trylock(&m_hndl) == 0;
        }
    }


private:
    version (Windows)
    {
        CRITICAL_SECTION    m_hndl;
    }
    else version (Posix)
    {
        pthread_mutex_t     m_hndl;
    }

    struct MonitorProxy
    {
        Object.Monitor link;
    }

    MonitorProxy            m_proxy;


package:
    version (Posix)
    {
        pthread_mutex_t* handleAddr()
        {
            return &m_hndl;
        }
    }
}

///
/* @safe nothrow -> see druntime PR 1726 */
// Test regular usage.
unittest
{
    import core.thread : Thread;

    class Resource
    {
        Mutex mtx;
        int cargo;

        this() shared @safe nothrow
        {
            mtx = new shared Mutex();
            cargo = 42;
        }

        void useResource() shared @safe nothrow @nogc
        {
            mtx.lock_nothrow();
            (cast() cargo) += 1;
            mtx.unlock_nothrow();
        }
    }

    shared Resource res = new shared Resource();

    auto otherThread = new Thread(
    {
        foreach (i; 0 .. 10000)
            res.useResource();
    }).start();

    foreach (i; 0 .. 10000)
        res.useResource();

    otherThread.join();

    assert (res.cargo == 20042);
}

// Test @nogc usage.
@system @nogc nothrow unittest
{
    import core.stdc.stdlib : malloc, free;

    void* p = malloc(__traits(classInstanceSize, Mutex));

    auto ti = typeid(Mutex);
    p[0 .. ti.initializer.length] = ti.initializer[];

    shared Mutex mtx = cast(shared(Mutex)) p;
    mtx.__ctor();

    mtx.lock_nothrow();

    { // test recursive locking
        mtx.tryLock_nothrow();
        mtx.unlock_nothrow();
    }

    mtx.unlock_nothrow();

    // In general destorying classes like this is not
    // safe, but since we know that the only base class
    // of Mutex is Object and it doesn't have a dtor
    // we can simply call the non-virtual __dtor() here.

    // Ok to cast away shared because destruction
    // should happen only from a single thread.
    (cast(Mutex) mtx).__dtor();

    // Verify that the underlying implementation has been destroyed by checking
    // that locking is not possible. This assumes that the underlying
    // implementation is well behaved and makes the object non-lockable upon
    // destruction. The Bionic, DragonFly, Musl, and Solaris C runtimes don't
    // appear to do so, so skip this test.
    version (CRuntime_Bionic) {} else
    version (CRuntime_Musl) {} else
    version (DragonFlyBSD) {} else
    version (Solaris) {} else
    assert(!mtx.tryLock_nothrow());

    free(cast(void*) mtx);
}

// Test single-thread (non-shared) use.
unittest
{
    Mutex m = new Mutex();

    m.lock();

    m.tryLock();
    m.unlock();

    m.unlock();
}

unittest
{
    import core.thread;

    auto mutex      = new Mutex;
    int  numThreads = 10;
    int  numTries   = 1000;
    int  lockCount  = 0;

    void testFn()
    {
        for (int i = 0; i < numTries; ++i)
        {
            synchronized (mutex)
            {
                ++lockCount;
            }
        }
    }

    auto group = new ThreadGroup;

    for (int i = 0; i < numThreads; ++i)
        group.create(&testFn);

    group.joinAll();
    assert(lockCount == numThreads * numTries);
}
