/* -*- mode: c++; indent-tabs-mode: nil -*- */
/** @file QC_ThreadPool.qpp ThreadPool class definition */
/*
    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/ThreadPool.h"

static void tpt_start_thread(ExceptionSink* xsink, ThreadPoolThread* tpt) {
   tpt->worker(xsink);
}

ThreadPoolThread::ThreadPoolThread(ThreadPool& n_tp, ExceptionSink* xsink) : tp(n_tp) {
    id = q_start_thread(xsink, (q_thread_t)tpt_start_thread, this);
    if (id > 0)
        tp.ref();
}

void ThreadPoolThread::worker(ExceptionSink* xsink) {
    SafeLocker sl(m);

    while (!stopflag || task) {
        if (!task) {
            //printd(5, "ThreadPoolThread::worker() id %d about to wait stopflag: %d task: %p\n", id, stopflag, task);
            c.wait(m);
            if (stopflag && !task)
                break;
        }

        assert(task);

        sl.unlock();
        task->run(xsink).discard(xsink);
        sl.lock();
        task->del(xsink);
        task = 0;

        if (stopflag || tp.done(this))
            break;
    }

    //printd(5, "ThreadPoolThread::worker() stopping id %d: %s\n", id, stopCond ? "wait" : "after detach");

    if (stopCond) {
        stopped = true;
        stopCond->signal();
    } else {
        sl.unlock();
        finalize(xsink);
    }
}

void ThreadPoolThread::finalize(ExceptionSink* xsink) {
    tp.deref(xsink);
    delete this;
}

static void tp_start_thread(ExceptionSink* xsink, ThreadPool* tp) {
    tp->worker(xsink);
}

ThreadPool::ThreadPool(ExceptionSink* xsink, int n_max, int n_minidle, int n_maxidle, int n_release_ms) :
    max(n_max), minidle(n_minidle), maxidle(n_maxidle), release_ms(n_release_ms) {
    assert(xsink);
    if (max < 0)
        max = 0;
    if (minidle < 0)
        minidle = 0;
    if (maxidle <= 0)
        maxidle = minidle;
    if (q_start_thread(xsink, (q_thread_t)tp_start_thread, this) == -1) {
        assert(*xsink);
        stopped = true;
    }
}

void ThreadPool::worker(ExceptionSink* xsink) {
    SafeLocker sl(m);

    for (int i = 0; i < minidle; ++i) {
        if (addIdleWorker(xsink)) {
            xsink->handleExceptions();
            break;
        }
    }

    while (!stopflag) {
        if (q.empty()) {
            if (shutdown) {
                stopflag = true;
                break;
            }
            if (release_ms && (int)fh.size() > minidle) {
                if (cond.wait(m, release_ms) && q.empty()) {
                    // timeout occurred: terminate an idle thread
                    ThreadPoolThread* tpt = fh.front();
                    //printd(5, "ThreadPool::worker() this: %p release_ms: %d timeout - stopping idle thread %p (minidle: %d maxidle: %d fh.size(): %ld)\n", this, release_ms, tpt, minidle, maxidle, fh.size());
                    fh.pop_front();
                    tpt->stop();
                    continue;
                }
            } else {
                cond.wait(m);
            }
        }

        if (stopflag)
            break;

        while (!q.empty()) {
            ThreadPoolThread* tpt = getThreadUnlocked(xsink);
            if (!tpt) {
                xsink->handleExceptions();
                break;
            }
            tpt->submit(q.front());
            q.pop_front();
        }

        while ((int)fh.size() < minidle && (!max || ((int)fh.size() + (int)ah.size() < max))) {
            if (addIdleWorker(xsink)) {
                xsink->handleExceptions();
                break;
            }
        }
    }

    // idle threads can be terminated with stop() in all cases
    for (auto& i : fh) {
        i->stop();
    }

    sl.unlock();

    if (!detach) {
        // signal worker threads to terminate and confirm termination inline
        for (auto& i : ah) {
            i->stopWait();
        }
        // wait for termination
        for (auto& i : ah) {
            i->stopConfirm(xsink);
        }
    } else {
        // detach threads and do not wait for termination
        for (auto& i : ah) {
            i->stop();
        }
    }

    sl.lock();

#ifdef DEBUG
    ah.clear();
    fh.clear();
#endif

    stopped = true;
    stopCond.broadcast();

    // issue #2906: we have to copy the task queue here to cancel them outside the lock because
    // the thread pool can be destroyed after we release the lock
    taskq_t q_copy = q;
    q.clear();

    sl.unlock();

    // cancel tasks if any
    for (auto& i : q_copy) {
        i->cancel(xsink);
        i->del(xsink);
    }
}

//! This class defines a thread pool that grows and shrinks dynamically within user-defined limits according to the task load placed on it
/** The ThreadPool can also pre-allocate idle threads for quickly allocating threads to tasks submitted through
    @ref Qore::Thread::ThreadPool::submit() "ThreadPool::submit()" for cases when very low latency is required (for example, for
    allocating already waiting threads to incoming @ref Qore::Socket "Socket" connections).

    A worker thread is started while the ThreadPool is running that waits for tasks in an internal task queue and allocates the tasks
    to worker threads.   If an idle thread is available, the task is submitted to that thread immediately, otherwise, if the ThreadPool
    is not already at maximum capacity (the \a max argument to @ref Qore::Thread::ThreadPool::constructor() "ThreadPool::constructor()"),
    a new thread is started and the task is allocated to the new thread.  Otherwise, the task will block until a thread becomes free, at
    which time the task is allocated to the newly-freed worker thread.

    When a worker thread has no more tasks to execute, it will either be returned to the pool to wait in an idle state if possible, or it
    will terminate.  Threads are returned to the idle pool if there are fewer than \a maxidle threads in the idle pool already or if
    there are more tasks in the queue than idle threads.

    ThreadPools downscale over time when demand is lower according to the \a release_ms argument to
    @ref Qore::Thread::ThreadPool::constructor() "ThreadPool::constructor()"; when there more than \a minidle threads in the idle pool,
    then for each time period defined by \a release_ms, a single thread in the idle pool is released until the number of threads in the
    idle pool reaches \a minidle.

    Therefore the \a minidle argument defines the "ground state" of the ThreadPool (how many idle threads are waiting for tasks), and
    the \a release_ms argument defines the period in which the ThreadPool returns to its ground state after demand for threads results
    in a condition where there are temporarily more than \a minidle threads in the idle pool.

    If the ThreadPool is stopped when tasks are still in the queue, then any cancellation @ref closure "closure" or
    @ref call_reference "call reference" for the task is executed; see @ref Qore::Thread::ThreadPool::submit() "ThreadPool::submit()"
    for more information.

    @par Example:
    @code{.py}
ThreadPool tp(10, 2, 4);
    @endcode

    @since %Qore 0.8.8
 */
qclass ThreadPool [dom=THREAD_CLASS; arg=ThreadPool* tp; ns=Qore::Thread];

//! creates the pool with the given parameters; idle worker threads are started immediately if necessary
/** @par Example:
    @code{.py}
ThreadPool tp(10, 2, 4);
    @endcode

    When worker threads complete, the thread is returned to the pool if there are less than \a minidle threads in the idle list or there are more tasks in the queue than the number of idle threads.

    @param max the maximum number of threads in the pool
    @param minidle the minimum number of free idle threads to keep ready
    @param maxidle the maximum number of idle threads to keep ready
    @param release_ms this value gives the delay in terminating single idle threads when \a maxidle > \a minidle and there are more than \a minidle threads in the idle pool; for example, if \a release_ms = \c 10s then when there are more than \a minidle threads in the idle pool, every 10 seconds an idle thread is terminated until there are \a minidle threads in the pool.  Note that like all %Qore functions and methods taking timeout values, a @ref relative_dates "relative date/time value" can be used to make the units clear (i.e. \c 2m = two minutes, etc.)

    @throw THREADPOOL-ERROR minidle > max, maxidle > max or minidle > maxidle, or release_ms < 0
 */
ThreadPool::constructor(int max = 0, int minidle = 0, int maxidle = 0, timeout release_ms = 5s) {
    if (max > 0) {
        if (minidle > max) {
            xsink->raiseException("THREADPOOL-ERROR", "cannot create a ThreadPool object with minidle (%d) > max (%d)", minidle, max);
            return;
        }
        if (maxidle > max) {
            xsink->raiseException("THREADPOOL-ERROR", "cannot create a ThreadPool object with maxidle (%d) > max (%d)", maxidle, max);
            return;
        }
    }
    if (maxidle > 0) {
        if (minidle > maxidle) {
            xsink->raiseException("THREADPOOL-ERROR", "cannot create a ThreadPool object with minidle (%d) > maxidle (%d)", minidle, maxidle);
            return;
        }
    }
    if (release_ms < 0) {
        xsink->raiseException("THREADPOOL-ERROR", "cannot create a ThreadPool object with release_ms < 0 (value passed: %d)", release_ms);
        return;
    }

    ReferenceHolder<ThreadPool> tp(new ThreadPool(xsink, max, minidle, maxidle, release_ms), xsink);
    if (*xsink)
        return;

    self->setPrivate(CID_THREADPOOL, tp.release());
}

//! destroys the pool; any worker threads are detached and pending tasks not yet executed are canceled; to wait for all worker threads to complete, call ThreadPool::stopWait() first
/** @par Example:
    @code{.py}
delete tp;
    @endcode
 */
ThreadPool::destructor() {
    tp->stop();
    tp->deref();
}

//! stops the thread pool and returns immediately; queued tasks are canceled
/** @par Example:
    @code{.py}
tp.stop();
    @endcode

    This method detaches all worker threads immediately, stops the ThreadPool, and returns immediately; after this
    method has been executed once no more tasks can be submitted to the ThreadPool.

    Queued tasks not yet processed by a worker thread are canceled.

    @note if any worker threads are still running; they are detached from the ThreadPool and terminate independently from the ThreadPool

    @see ThreadPool::stopWait()
 */
ThreadPool::stop() {
    tp->stop();
}

//! stops the thread pool and does not return until all submitted tasks have been executed and all worker threads have been stopped
/** @par Example:
    @code{.py}
tp.stopWait();
    @endcode

    This method does not return until the ThreadPool has been stopped and all worker threads have also been stopped.
    After this method has been executed once no more tasks can be submitted to the ThreadPool.

    @see ThreadPool::stop()
 */
ThreadPool::stopWait() {
    tp->stopWait(xsink);
}

//! submit a task to the pool
/** @par Example:
    @code{.py}
tp.submit(sub () { call_function(arg); });
    @endcode

    @param task the @ref closure "closure" or @ref call_reference "call reference" to execute
    @param cancel an optional  @ref closure "closure" or @ref call_reference "call reference" to execute if the ThreadPool is stopped before the task can be executed; note that cancellation code is run serially for each task in order of submission in the ThreadPool's worker thread after the ThreadPool has been shut down
 */
ThreadPool::submit(code task, *code cancel) {
    tp->submit(task->refRefSelf(), cancel ? cancel->refRefSelf() : 0, xsink);
}

//! returns a description of the ThreadPool
/** @par Example:
    @code{.py}
string desc = tp.toString();
    @endcode

    @return a description of the ThreadPool
 */
string ThreadPool::toString() [flags=CONSTANT] {
   QoreStringNode* str = new QoreStringNode;
   tp->toString(*str);
   return str;
}
