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

    POSIX thread library for Qore

    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/ql_thread.h"
#include "qore/intern/QC_TimeZone.h"
#include "qore/intern/qore_program_private.h"
#include "qore/intern/QC_AbstractThreadResource.h"
#include "qore/intern/QoreHashNodeIntern.h"
#include "qore/intern/QC_TimeZone.h"

#include <pthread.h>
#include <thread>

extern QoreClass* QC_TIMEZONE;

static void delete_thread_data_intern(const QoreListNode* args, ExceptionSink* xsink) {
    assert(xsink);
    assert(args);
    // get thread data hash
    QoreHashNode *data = getProgram()->getThreadData();

    // iterate through arguments and delete each key
    for (unsigned i = 0; i < args->size(); i++) {
        QoreValue p = args->retrieveEntry(i);
        if (!p.isNothing()) {
            QoreStringValueHelper t(p, QCS_DEFAULT, xsink);
            if (*xsink)
                break;

            data->deleteKey(*t, xsink);
            if (*xsink)
                break;
        }
    }
}

static QoreHashNode* remove_thread_data_intern(const QoreListNode* args, ExceptionSink* xsink) {
    assert(xsink);
    assert(args);
    // get thread data hash
    QoreHashNode* data = getProgram()->getThreadData();
    qore_hash_private* dh = qore_hash_private::get(*data);

    ReferenceHolder<QoreHashNode> rv(new QoreHashNode(autoTypeInfo), xsink);

    // iterate through arguments and remove each key
    for (unsigned i = 0; i < args->size(); i++) {
        QoreValue p = args->retrieveEntry(i);
        if (!p.isNothing()) {
            QoreStringValueHelper t(p, QCS_DEFAULT, xsink);
            if (*xsink) {
                return nullptr;
            }

            rv->setKeyValue(*t, dh->takeKeyValueIntern((*t)->c_str()), xsink);
            assert(!*xsink);
        }
    }

    return rv.release();
}

//! call stack hash description
/** @since %Qore 0.8.13
*/
hashdecl CallStackInfo {
    //! function or method name
    string function;

    //! starting line number
    int line;

    //! ending line number
    int endline;

    //! The parse label of the code; this is normally the file name; this corresponds to the label parameter of the Program::parse() and Program::parsePending() methods and the Qore::parse() function, for example (if known, for user code only)
    string file;

    //! An optional source string; if multiple sections of a file were parsed with different parse labels, then the source file name will normally go here and the "file" key will have the parse label; this corresponds to the source parameter of the Program::parse() and Program::parsePending() methods, for example (if known, for user code only)
    string source;

    //! The line number offset for the \c "source" key (if known, for user code only)
    int offset;

    //! the call type; see @ref call_type_constants for possible values
    int typecode;

    //! a type string; either \c "user", \c "builtin", \c "new-thread" or \c "rethrow" (for rethrown exceptions)
    string type;

    //! the language of the source code; normally \c "Qore"
    string lang;

    //! related program id (since 0.8.13.7)
    *int programid;

    //! related statement id (since 0.8.13.7)
    *int statementid;
}

/** @defgroup threading_functions Threading Functions
    Threading functions
 */
///@{
//! Returns the %Qore thread ID (TID) of the current thread
/**
    @return the %Qore thread ID (TID) of the current thread

    @par Example:
    @code{.py}
int tid = gettid();
    @endcode

    @note this function is not flagged with @ref CONSTANT since its value could change at runtime
*/
int gettid() [flags=RET_VALUE_ONLY;dom=THREAD_INFO] {
    return q_gettid();
}

//! Returns the current number of threads in the process (not including the special @ref signal_handling "signal handling thread")
/**
    @return the current number of threads in the process (not including the special @ref signal_handling "signal handling thread")

    @par Example:
    @code{.py}
int num = num_threads();
    @endcode

    @note this function is not flagged with @ref CONSTANT since its value could change at runtime
*/
int num_threads() [flags=RET_VALUE_ONLY;dom=THREAD_INFO] {
    return thread_list.getNumThreads();
}

//! Returns a list of all current thread IDs
/** Note that the special signal handling thread with TID 0 is never included in the list returned by this function

    @return a list of all current thread IDs

    @par Example:
    @code{.py}
list<int> l = thread_list();
    @endcode

    @note this function is not flagged with @ref CONSTANT since its value could change at runtime
*/
list<int> thread_list() [flags=RET_VALUE_ONLY;dom=THREAD_INFO] {
    return get_thread_list();
}

//! Saves the data passed in the thread-local hash; all keys are merged into the thread-local hash, overwriting any information that may have been there before
/** @param h a hash of data to save in the thread-local data hash

    @par Example:
    @code{.py}
save_thread_data(h);
    @endcode

    @note This function does not throw any exceptions, however if a value is removed from the thread-local data hash
    by being overwritten with a new value, and the value is an object that goes out of scope, then such an object
    could throw an exception in its destructor
*/
nothing save_thread_data(hash<auto> h) [dom=THREAD_CONTROL] {
    QoreHashNode* data = getProgram()->getThreadData();
    data->merge(h, xsink);
}

//! Saves the data passed against the key passed as an argument in thread-local storage
/** @param key the name of the key in the thread-local hash to save the data against
    @param value the value to save in the thread-local hash against the key

    @par Example:
    @code{.py}
save_thread_data("key1", value);
    @endcode

    @note This function does not throw any exceptions, however if a value is removed from the thread-local data hash
    by being overwritten with a new value, and the value is an object that goes out of scope, then such an object
    could throw an exception in its destructor
*/
nothing save_thread_data(string key, auto value) [dom=THREAD_CONTROL] {
    QoreHashNode* data = getProgram()->getThreadData();
    data->setKeyValue(key, value.refSelf(), xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing save_thread_data() [dom=THREAD_CONTROL;flags=RUNTIME_NOOP] {
}

//! Deletes the data associated to one or more keys in the thread-local data hash; if the data is an object, then it is destroyed
/**
    @param ... Deletes the data associated to one or more keys in the thread-local data hash corresponding to each
    string argument in the top-level argument list; arguments are converted to strings if necessary

    @par Example:
    @code{.py}
delete_thread_data("key1", "key2");
    @endcode

    @note This function does not throw any exceptions, however if an object is deleted from the thread-local data
    hash, then it could throw an exception in its destructor

    @see remove_thread_data() for a similar function that does not explicitly destroy objects in the thread-local data
    hash
*/
nothing delete_thread_data(...) [dom=THREAD_CONTROL] {
    if (num_args(args))
        delete_thread_data_intern(args, xsink);
}

//! Deletes the data associated to one or more keys in the thread-local data hash; if the data is an object, then it is destroyed
/**
    @param l a list of key names for deleting keys in the thread-local data hash; values are converted to strings if
    necessary

    @par Example:
    @code{.py}
delete_thread_data(list_of_keys);
    @endcode

    @note This function does not throw any exceptions, however if an object is deleted from the thread-local data
    hash, then it could throw an exception in its destructor

    @see remove_thread_data() for a similar function that does not explicitly destroy objects in the thread-local data
    hash
*/
nothing delete_thread_data(list<softstring> l) [dom=THREAD_CONTROL] {
    delete_thread_data_intern(l, xsink);
}

//! Removes the data associated to one or more keys in the thread-local data hash and returns the data removed
/**
    @param ... the key names should be given directly in the argument list with this variant.  If the given hash keys
    do not exist in the thread-local data hash, then the given key in the return value will have no value assigned

    @return a hash of the data removed or @ref nothing if no arguments were passed to the function

    @par Example:
    @code{.py}
hash<auto> h = remove_thread_data("filename");
    @endcode

    @since %Qore 0.8.4 this function returns the values it removes
*/
*hash<auto> remove_thread_data(...) [dom=THREAD_CONTROL] {
    return num_args(args) ? remove_thread_data_intern(args, xsink) : nullptr;
}

//! Removes the data associated to one or more keys in the thread-local data hash from a literal list passed as the first argument and returns the data removed
/**
    @param l a list of key names to remove from the thread-local data hash.  If the given hash keys do not exist in
    the thread-local data hash, then the given key in the return value will have no value assigned

    @return a hash of the data removed

    @par Example:
    @code{.py}
hash<auto> h = remove_thread_data(key_list);
    @endcode

    @since %Qore 0.8.4 this function returns the values it removes
*/
hash<auto> remove_thread_data(list<softstring> l) [dom=THREAD_CONTROL] {
    return remove_thread_data_intern(l, xsink);
}

//! Deletes all keys in the thread-local data hash
/**
    @par Example:
    @code{.py}
delete_all_thread_data();
    @endcode

    @note This function does not throw any exceptions, however if an object is deleted from the thread-local data hash, then it could throw an exception in its destructor
*/
nothing delete_all_thread_data() [dom=THREAD_CONTROL] {
    qore_program_private::clearThreadData(*getProgram(), xsink);
}

//! Returns the value of the thread-local data attached to the key passed
/**
    @return the value of the thread-local data attached to the key passed

    @par Example:
    @code{.py}
auto data = get_thread_data("key1");
    @endcode
*/
auto get_thread_data(string key) [flags=RET_VALUE_ONLY;dom=THREAD_CONTROL,THREAD_INFO] {
    TempEncodingHelper tmp(key, QCS_DEFAULT, xsink);
    if (!tmp)
        return QoreValue();
    const QoreHashNode* h = getProgram()->getThreadData();
    return qore_hash_private::get(*h)->getReferencedKeyValueIntern(tmp->c_str());
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing get_thread_data() [dom=THREAD_CONTROL,THREAD_INFO;flags=RUNTIME_NOOP] {
}

//! Returns the entire thread-local data hash
/**
    @return the entire thread-local data hash

    @par Example:
    @code{.py}
hash<auto> th = get_all_thread_data();
    @endcode

    @note this function is not flagged with @ref CONSTANT since its value could change at runtime
*/
hash<auto> get_all_thread_data() [flags=RET_VALUE_ONLY;dom=THREAD_CONTROL,THREAD_INFO] {
    const QoreHashNode* h = getProgram()->getThreadData();
    return h->copy();
}

//! Returns a hash of lists of @ref CallStackInfo hashes keyed by TID (thread ID)
/**
    @return a hash of lists of @ref CallStackInfo hashes keyed by TID (thread ID)

    @deprecated use get_all_thread_call_stacks(); camel-case function names were deprecated in %Qore 0.8.12
*/
hash<string, list<hash<CallStackInfo>>> getAllThreadCallStacks() [dom=THREAD_CONTROL,THREAD_INFO;flags=DEPRECATED] {
    return getAllCallStacks();
}

//! Returns a hash of lists of @ref CallStackInfo hashes keyed by TID (thread ID)
/**
    @return a hash of lists of @ref CallStackInfo hashes keyed by TID (thread ID)

    @par Example:
    @code{.py}
hash<string, list<hash<CallStackInfo>>> cs = get_all_thread_call_stacks();
foreach string tid in (cs.keyIterator()) {
    printf("TID %d\n", tid);
    int i;
    foreach hash<CallStackInfo> l in (cs{tid}) {
        if (l.type != "new-thread") {
            printf(" %d: %s() called at %s:%d (%s function)\n", ++i, l.function, l.file, l.line, l.type);
        } else {
            printf(" %d: *** thread started by background operator ***\n", ++i);
        }
    }
}
    @endcode

    @since %Qore 0.8.12 as a replacement for deprecated camel-case getAllThreadCallStacks()
*/
hash<string, list<hash<CallStackInfo>>> get_all_thread_call_stacks() [dom=THREAD_CONTROL,THREAD_INFO] {
    return getAllCallStacks();
}

//! Returns a list of @ref CallStackInfo hashes for the current TID (thread ID); because it is always from the same thread being read, no locking is applied as in @ref get_all_thread_call_stacks()
/**
    @return a list of @ref CallStackInfo hashes for the current TID (thread ID)

    @par Example:
    @code{.py}
list<hash<CallStackInfo>> cs = get_thread_call_stack();
foreach hash<CallStackInfo> l in (cs) {
    if (l.type != "new-thread") {
        printf(" %d: %s() called at %s:%d (%s function)\n", ++i, l.function, l.file, l.line, l.type);
    } else {
        printf(" %d: *** thread started by background operator ***\n", ++i);
    }
}
    @endcode

    @since %Qore 0.8.13
*/
list<hash<CallStackInfo>> get_thread_call_stack() [dom=THREAD_CONTROL,THREAD_INFO] {
    return qore_get_thread_call_stack();
}

//! Immediately runs all thread resource cleanup routines for the current thread and throws all associated exceptions
/** This function is particularly useful when used in combination with embedded code in order to catch (and log, for
    example) thread resource errors (ex: uncommitted transactions, unlocked locks, etc) - this can be used when
    control returns to the "master" program to ensure that no thread-local resources have been left active.

    This function will run all thread resource cleanup routines even if mark_thread_resources() has been called (i.e.
    it clears all marks as well).

    When exceptions are thrown by this function, thread-local resources are also cleaned up at the same time.

    @see
    - @ref thread_resources
    - mark_thread_resources()
    - remove_thread_resource()
    - set_thread_resource()
    - throw_thread_resource_exceptions()
    - throw_thread_resource_exceptions_to_mark()
    - @ref Qore::Thread::AbstractThreadResource "AbstractThreadResource"

    @deprecated use throw_thread_resource_exceptions(); camel-case function names were deprecated in %Qore 0.8.12
*/
nothing throwThreadResourceExceptions() [dom=THREAD_CONTROL;flags=DEPRECATED] {
    purge_thread_resources(xsink);
}

//! Immediately runs all thread resource cleanup routines for the current thread and throws all associated exceptions
/** This function is particularly useful when used in combination with embedded code in order to catch (and log, for
    example) thread resource errors (ex: uncommitted transactions, unlocked locks, etc) - this can be used when
    control returns to the "master" program to ensure that no thread-local resources have been left active.

    This function will run all thread resource cleanup routines even if mark_thread_resources() has been called (i.e.
    it clears all marks as well).

    When exceptions are thrown by this function, thread-local resources are also cleaned up at the same time.

    @par Example:
    @code{.py}
try {
    throw_thread_resource_exceptions();
} catch (hash<ExceptionInfo> ex) {
    # ... log or handle exceptions
}
    @endcode

    @note may not throw an exception even if there are thread resources in place that get cleaned up in case the
    @ref Qore::Thread::AbstractThreadResource::cleanup() method performs the cleanup but does not throw an exception

    @see
    - @ref thread_resources
    - mark_thread_resources()
    - remove_thread_resource()
    - set_thread_resource()
    - throw_thread_resource_exceptions_to_mark()
    - @ref Qore::Thread::AbstractThreadResource "AbstractThreadResource"

    @since %Qore 0.8.12 as a replacement for deprecated camel-case throwThreadResourceExceptions()
*/
nothing throw_thread_resource_exceptions() [dom=THREAD_CONTROL] {
    purge_thread_resources(xsink);
}

//! Immediately runs all thread resource cleanup routines for the current thread for thread resources created since the last call to mark_thread_resources() and throws all associated exceptions
/**
    When exceptions are thrown by this function, thread-local resources are also cleaned up at the same time.

    @return @ref True if there are additional thread resource marks to process, @ref False if there are no more

    @par Example:
    @code{.py}
try {
    mark_thread_resources();
    # ... some code or calls that may allocate thread resources

    # calling the following will ensure that any thread-resources allocated since
    # the last call to mark_thread_resources() will be cleaned up and associated
    # exceptions will be thrown
    throw_thread_resource_exceptions_to_mark();
} catch (hash<<ExceptionInfo> ex) {
    # ... log or handle exceptions
}
    @endcode

    @note may not throw an exception even if there are thread resources in place that get cleaned up in case the
    @ref Qore::Thread::AbstractThreadResource::cleanup() method performs the cleanup but does not throw an exception

    @see
    - @ref thread_resources
    - mark_thread_resources()
    - remove_thread_resource()
    - set_thread_resource()
    - throw_thread_resource_exceptions()
    - @ref Qore::Thread::AbstractThreadResource "AbstractThreadResource"

    @since %Qore 0.8.4
 */
bool throw_thread_resource_exceptions_to_mark() [dom=THREAD_CONTROL] {
    return (bool)purge_thread_resources_to_mark(xsink);
}

//! Marks thread resources so that any thread resources left allocated after this call will be cleaned up when throw_thread_resource_exceptions_to_mark() is called
/**
    When exceptions are thrown by this function, thread-local resources are also cleaned up at the same time.

    @par Example:
    @code{.py}
try {
    mark_thread_resources();

    # calling the following function will ensure that any thread-resources
    # allocated since the last call to mark_thread_resources() will be cleaned
    # up and associated exceptions will be thrown
    on_exit throw_thread_resource_exceptions_to_mark();

    # ... some code or calls that may allocate thread resources
} catch (hash<ExceptionInfo> ex) {
    # ... log or handle exceptions
}
    @endcode

    @see
    - @ref thread_resources
    - remove_thread_resource()
    - set_thread_resource()
    - throw_thread_resource_exceptions()
    - throw_thread_resource_exceptions_to_mark()
    - @ref Qore::Thread::AbstractThreadResource "AbstractThreadResource"

    @since %Qore 0.8.4
 */
nothing mark_thread_resources() [dom=THREAD_CONTROL] {
    mark_thread_resources();
}

//! Sets a @ref call_reference "call reference" or @ref closure "closure" to run every time a new thread is started
/** This code can be used to initialize @ref threading_and_variables "global thread-local variables", for example.

    @param init a @ref call_reference "call reference" or @ref closure "closure" to run every time a new thread is
    started or @ref nothing to clear any thread initialization code

    @return @ref True if there was already user initialization code set, @ref False if not

    @par Example:
    @code{.py}
set_thread_init(sub () { var = 123; });
    @endcode

    @note the code will be run for all new threads, but is not run by this function for the current thread

    @see @ref Qore::Program::setThreadInit()

    @since %Qore 0.8.13 added the ability to remove the thread initialization code by passing @ref nothing to this
    function
*/
bool set_thread_init(*code init) [dom=THREAD_CONTROL] {
    return qore_program_private::setThreadInit(*getProgram(), init, xsink);
}

//! Sets the default time zone for the current thread
/** @param zone the TimeZone object for the current thread

    This will cause the TimeZone::get() method to return the given TimeZone when called from the same thread

    @par Example:
    @code{.py}
*TimeZone tz = get_thread_tz();
set_thread_tz(new TimeZone("Europe/Prague"));
on_exit set_thread_tz(tz);
    @endcode

    @note The TimeZone will only be set for the current thread in the current Program

    @see
    - set_thread_tz()
    - get_thread_tz()

    @since %Qore 0.8.5
 */
nothing set_thread_tz(TimeZone[TimeZoneData] zone) [dom=LOCALE_CONTROL] {
    ReferenceHolder<TimeZoneData> holder(zone, xsink);
    set_thread_tz(zone->get());
}

//! Clears the thread-local time zone for the current thread; after this call TimeZone::get() will return the value set for the current Program
/** @par Example:
    @code{.py}
*TimeZone tz = get_thread_tz();
set_thread_tz(new TimeZone("Europe/Prague"));
on_exit set_thread_tz(tz);
    @endcode

    @note The TimeZone will only be cleared in the current thread in the current Program

    @see
    - set_thread_tz(TimeZone)
    - get_thread_tz()

    @since %Qore 0.8.5
 */
nothing set_thread_tz() [dom=LOCALE_CONTROL] {
    clear_thread_tz();
}

//! Returns any TimeZone set for the current thread, @ref nothing if none is set
/** @return any TimeZone set for the current thread, @ref nothing if none is set

    @par Example:
    @code{.py}
*TimeZone tz = get_thread_tz();
set_thread_tz(new TimeZone("Europe/Prague"));
on_exit set_thread_tz(tz);
    @endcode

    @see
    - set_thread_tz(TimeZone)
    - set_thread_tz()

    @since %Qore 0.8.5
 */
*TimeZone get_thread_tz() [dom=LOCALE_CONTROL] {
    bool set;
    const AbstractQoreZoneInfo* tz = get_thread_tz(set);
    if (!set)
        return QoreValue();
    return new QoreObject(QC_TIMEZONE, 0, new TimeZoneData(tz));
}

//! sets a thread resource for the current thread
/** @par Example:
    @code{.py}
set_thread_resource(obj);
    @endcode

    @param resource the thread resource to set

    @see
    - @ref thread_resources
    - mark_thread_resources()
    - remove_thread_resource()
    - throw_thread_resource_exceptions()
    - throw_thread_resource_exceptions_to_mark()
    - @ref Qore::Thread::AbstractThreadResource "AbstractThreadResource"

    @since %Qore 0.8.12
 */
nothing set_thread_resource(AbstractThreadResource[AbstractQoreThreadResource] resource) [dom=THREAD_CONTROL] {
    ReferenceHolder<AbstractQoreThreadResource> holder(resource, xsink);
    set_thread_resource(resource);
}

//! removes a thread resource from the current thread
/** @par Example:
    @code{.py}
remove_thread_resource(obj);
    @endcode

    @param resource the thread resource to remove

    @return True if the resource was removed, False if not

    @see
    - @ref thread_resources
    - mark_thread_resources()
    - set_thread_resource()
    - throw_thread_resource_exceptions()
    - throw_thread_resource_exceptions_to_mark()
    - @ref Qore::Thread::AbstractThreadResource "AbstractThreadResource"

    @since %Qore 0.8.12
 */
bool remove_thread_resource(AbstractThreadResource[AbstractQoreThreadResource] resource) [dom=THREAD_CONTROL] {
    ReferenceHolder<AbstractQoreThreadResource> holder(resource, xsink);
    return remove_thread_resource(resource) ? false : true;
}

//! sets a callable thread resource for the current thread with an optional argument
/** @par Example:
    @code{.py}
set_thread_resource(\func(), code);
    @endcode

    @param resource the thread resource handler to set
    @param arg an argument to use when calling the thread resource handler

    @see
    - @ref thread_resources
    - mark_thread_resources()
    - remove_thread_resource()
    - throw_thread_resource_exceptions()
    - throw_thread_resource_exceptions_to_mark()
    - @ref Qore::Thread::AbstractThreadResource "AbstractThreadResource"

    @since %Qore 0.8.12
 */
nothing set_thread_resource(code resource, auto arg) [dom=THREAD_CONTROL] {
    set_thread_resource(resource, arg);
}

//! removes a callable thread resource from the current thread
/** @par Example:
    @code{.py}
remove_thread_resource(\func());
    @endcode

    @param resource the callable thread resource to remove

    @return True if the resource was removed, False if not

    @see
    - @ref thread_resources
    - mark_thread_resources()
    - set_thread_resource()
    - throw_thread_resource_exceptions()
    - throw_thread_resource_exceptions_to_mark()
    - @ref Qore::Thread::AbstractThreadResource "AbstractThreadResource"

    @since %Qore 0.8.12
 */
bool remove_thread_resource(code resource) [dom=THREAD_CONTROL] {
    return remove_thread_resource(resource, xsink) ? false : true;
}

//! yields the current thread so other threads can be scheduled and run
/** @par Example:
    @code{.py}
thread_yield();
    @endcode

    @since %Qore 0.8.13
*/
nothing thread_yield() [dom=PROCESS] {
#ifdef _Q_WINDOWS
    // cnanot find a working mingw c++ compiler with support for std::this_thread
    SwitchToThread();
#else
    std::this_thread::yield();
#endif
}

//! returns the default thread stack size
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_MANAGE_STACK

    @par Example:
    @code{.py}
int size = get_default_thread_stack_size();
    @endcode

    @note if the thread stack size has been changed since this thread was started, then the value returned by this
    function will not match the current thread's stack size

    @since %Qore 0.9.0
 */
int get_default_thread_stack_size() [dom=THREAD_INFO;flags=CONSTANT] {
#ifdef QORE_MANAGE_STACK
    return q_thread_get_stack_size();
#else
    return xsink->raiseException("MISSING-FEATURE-ERROR", "this version of the Qore library was built without "
        "support for stack management; check Qore::Option::HAVE_MANAGE_STACK before calling");
#endif
}

//! sets the default thread stack size for new threads created after this call and returns the stack size actually set
/** @par Platform Availability:
    @ref Qore::Option::HAVE_MANAGE_STACK

    @par Example:
    @code{.py}
int size = set_default_thread_stack_size(size);
    @endcode

    @param size the size in bytes for new threads

    @return the value actually set

    @throw SET-DEFAULT-THREAD-STACK-SIZE-ERROR an error occurred setting the default thread stack size

    @since %Qore 0.9.0
*/
int set_default_thread_stack_size(int size) [dom=THREAD_CONTROL] {
#ifdef QORE_MANAGE_STACK
    return q_thread_set_stack_size((size_t)size, xsink);
#else
    return xsink->raiseException("MISSING-FEATURE-ERROR", "this version of the Qore library was built without "
        "support for stack management; check Qore::Option::HAVE_MANAGE_STACK before calling");
#endif
}

//! returns the current thread's stack size
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_GET_STACK_SIZE

    @par Example:
    @code{.py}
int size = get_stack_size();
    @endcode

    @return the current thread's stack size in bytes

    @note on Linux, this always returns 8MB for TID 1 (the initial thread) even if the actual stack
    size is smaller, as Linux automatically extends the initial thread's stack when there is a stack
    overflow

    @since %Qore 0.9.0
 */
int get_stack_size() [dom=THREAD_INFO;flags=CONSTANT] {
#ifdef QORE_HAVE_GET_STACK_SIZE
    return q_thread_get_this_stack_size();
#else
    return xsink->raiseException("MISSING-FEATURE-ERROR", "this version of the Qore library was built without "
        "support for stack management; check Qore::Option::HAVE_GET_STACK_SIZE before calling");
#endif
}

//! sets the current thread's descriptive name
/** @par Platform Availability:
    @ref Qore::Option::HAVE_THREAD_NAME

    @par Example:
    @code{.py}
set_thread_name(name);
    @endcode

    @param name the new descriptive name for the thread

    @since %Qore 0.9.0
*/
nothing set_thread_name(string name) [dom=THREAD_CONTROL] {
#ifdef QORE_HAVE_THREAD_NAME
    q_set_thread_name(name->c_str());
#else
    xsink->raiseException("MISSING-FEATURE-ERROR", "this version of the Qore library was built without support for "
        "thread names; check Qore::Option::HAVE_THREAD_NAME before calling");
#endif
}

//! returns the current thread's descriptive name if set or @ref nothing if not
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_THREAD_NAME

    @par Example:
    @code{.py}
*string name = get_thread_name();
    @endcode

    @return the current thread's descriptive name if set or @ref nothing if not

    @note
    - if supported, the default thread name will be <tt>qore/</tt><i>tid</i> where \a tid is the current thread ID
    - in case the current platform does not support thread names, @ref nothing is always returned

    @since %Qore 0.9.0
*/
*string get_thread_name() [dom=THREAD_CONTROL;flags=CONSTANT] {
#ifdef QORE_HAVE_THREAD_NAME
    SimpleRefHolder<QoreStringNode> rv(new QoreStringNode);
    q_get_thread_name(**rv);
    return rv->empty() ? QoreValue() : rv.release();
#else
    return QoreValue();
#endif
}
///@}
