

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of CMSDK version 2.1.1.260726 (26 July 2026)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2026
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  and is released under the following
//_/_/  
//_/_/  EXTENDED BSD License
//_/_/
//_/_/  Redistribution and use in source and binary forms, with or without
//_/_/  modification, is permitted provided that the following conditions 
//_/_/  are met:
//_/_/
//_/_/  - Redistributions of source code must retain the above copyright 
//_/_/    and collaboration notice, this list of conditions and the following 
//_/_/    disclaimer.
//_/_/
//_/_/  - Redistributions in binary form must reproduce the above copyright 
//_/_/    notice, this list of conditions and the following disclaimer in the 
//_/_/    documentation and/or other materials provided with the distribution.
//_/_/
//_/_/  - Neither the name of its copyright holders nor the names of its 
//_/_/    contributors may be used to endorse or promote products derived from 
//_/_/    this software without specific prior written permission.
//_/_/
//_/_/  - CADIA Clause: The license granted in and to the software under this 
//_/_/    agreement is a limited-use license. The software may not be used in 
//_/_/    furtherance of: (i) intentionally causing bodily injury or severe 
//_/_/    emotional harm to any person; (ii) invading the personal privacy or 
//_/_/    violating the human rights of any person; or (iii) committing or 
//_/_/    preparing for any act of war. 
//_/_/
//_/_/  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
//_/_/  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
//_/_/  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
//_/_/  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
//_/_/  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
//_/_/  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
//_/_/  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
//_/_/  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
//_/_/  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
//_/_/  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
//_/_/  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
/*! @file ThreadManager.h
    @brief Process-wide thread registry and lifecycle manager: the concurrency core of CMSDK.

    ThreadManager is a singleton that owns the creation, monitoring and
    termination of every native thread the SDK spawns. All thread bookkeeping
    lives in one contiguous, relocatable memory block (header + occupancy
    bitfield + per-thread ThreadStats array), which makes it cheap to snapshot
    and to publish to diagnostic tools.

    <b>The Psyclone threading model in one paragraph.</b> Higher layers map
    work onto threads in two ways: a <em>continuous crank</em> owns a dedicated
    thread for its whole lifetime (created here, terminated here), while a
    <em>one-shot crank</em> borrows a pool thread each time its trigger fires.
    Because each firing gets its own thread, the same crank function can be
    executing concurrently on several threads at once; any state it shares
    must therefore be independently synchronised. ThreadManager itself only
    provides the raw threads and their statistics; the pooling/queueing policy
    lives in the higher-level scheduling code.

    <b>Platform notes.</b> The actual thread primitives are wrapped in
    utils:: (utils::CreateThread, utils::TerminateThread, ...): Windows uses
    CreateThread/SuspendThread/ResumeThread and CRITICAL_SECTION-backed
    mutexes, POSIX uses pthreads. On pthreads there is no portable way to read
    another thread's CPU usage, so threads must self-report via
    AddLocalThreadStats() (the THREAD_STATS_ADHOC collection policy); on
    Windows the built-in monitoring thread can sample every thread centrally.

    @defgroup Threading Threading
    @brief Thread creation, lifecycle control and per-thread CPU statistics.

    @par Why a threading layer of its own
    CMSDK predates a usable std::thread and, more importantly, needs more
    than thread creation: every thread in a Psyclone node is registered
    centrally so diagnostic tools can enumerate them, sample their CPU load
    and pause/interrupt/terminate them by id. Writing this against pthreads
    on Linux/macOS and the Win32 thread API on Windows directly would mean
    two divergent code paths in every caller; instead ThreadManager exposes
    one portable, id-based API (backed by the utils:: wrappers -
    utils::CreateThread, utils::TerminateThread, CRITICAL_SECTION- or
    pthread_mutex-backed cmlabs::utils::Mutex, and semaphores) and keeps all
    bookkeeping in one contiguous ThreadStats block that is cheap to snapshot.

    @par Pool threads vs owned threads
    Higher Psyclone layers use threads in two ways: a <em>continuous
    crank</em> owns a dedicated thread for its whole lifetime, while a
    <em>one-shot crank</em> borrows a pool thread for each trigger firing -
    so the same function may run on several threads at once. ThreadManager
    itself only provides the raw threads and statistics; pooling/queueing
    policy lives in the higher-level scheduling code.

    @par Starting a worker and guarding shared state
    @code
    static utils::Mutex statsMutex;      // protects sharedCounter
    static uint64 sharedCounter = 0;

    THREAD_RET THREAD_FUNCTION_CALL Worker(void* arg) {
        ThreadManager::AddLocalThreadStats();     // self-report on pthreads
        while (running) {
            // ... do a unit of work ...
            if (statsMutex.enter(1000, __FUNCTION__)) { // 1 s timeout
                sharedCounter++;                        // critical section
                statsMutex.leave();
            }
        }
        return 0;
    }

    uint32 tid;
    ThreadManager::CreateThread((THREAD_FUNCTION)Worker, NULL, tid);
    // ... later ...
    ThreadStats st = ThreadManager::GetThreadStats(tid); // CPU usage etc.
    ThreadManager::TerminateThread(tid);                 // last resort only
    @endcode

    @warning Concurrency rules: any state reachable from more than one thread
    must be independently synchronised (utils::Mutex / utils::Semaphore or a
    shared-memory named mutex); one-shot crank functions in particular must
    assume they run concurrently with themselves. TerminateThread() kills a
    thread without unwinding - locks it holds stay held and resources leak -
    so prefer cooperative shutdown flags and use termination only for hung
    threads.

    @note On pthreads there is no portable way to read another thread's CPU
    usage, so worker threads should call AddLocalThreadStats() once at startup
    (THREAD_STATS_ADHOC policy); on Windows the built-in monitoring thread
    samples every registered thread centrally.

    @see ThreadManager, ThreadStats, utils::Mutex, utils::Semaphore, @ref Memory
*/
#if !defined(_THREADMANAGER_H_)
#define _THREADMANAGER_H_

#include "MemoryManager.h"

namespace cmlabs {

/*! @brief Bookkeeping record for a single managed thread.
    @ingroup Threading

    One ThreadStats lives in the ThreadManager's contiguous storage block per
    thread slot. The @c time / @c currentCPUTicks arrays are rolling windows:
    index 0 is the most recent sample and each monitoring pass shifts the
    history down, giving up to 10 samples for CPU-load estimation.

    @note This is a plain POD copied around by value; it must stay trivially
          copyable because the storage block is (re)allocated with
          malloc/memcpy. */
struct ThreadStats {
	uint32 id;				//!< ThreadManager-assigned slot ID (index into the stats array)
	uint64 created;			//!< Creation time (microseconds, GetTimeNow()); 0 = slot unused/invalid
	uint32 status;			//!< Lifecycle state (THREAD_NONE/INIT/RUNNING/PAUSED/INTERRUPTED/TERMINATED)
	uint32 osID;			//!< Operating-system thread ID (as reported by the OS, not the slot ID)
	ThreadHandle hThread;	//!< Native handle (HANDLE on Windows, pthread_t wrapper on POSIX)
	THREAD_FUNCTION func;	//!< Entry function, retained so InterruptThread() can restart the thread
	void* args;				//!< Argument passed to @c func, retained for restart
	uint64 time[10];		//!< Rolling timestamps of the last 10 statistics samples (newest first)
	uint64 currentCPUTicks[10];	//!< Rolling CPU-tick readings matching @c time (newest first)
};

/*! @brief Header at the start of the ThreadManager's contiguous storage block.
    @ingroup Threading

    The block layout is: ThreadDataHeader, then an occupancy bitfield of
    @c bitFieldSize bytes (one bit per slot; see GETTHREADSTATS), then
    @c count ThreadStats records. The whole block is a single malloc'ed
    region so it can be resized and snapshotted atomically under the
    manager's mutex. */
struct ThreadDataHeader {
	uint32 size;			//!< Total size of the storage block in bytes
	uint32 count;			//!< Capacity: number of ThreadStats slots allocated
	uint32 activeCount;		//!< Number of slots currently holding a live (non-terminated) thread
	uint32 statColPolicy;	//!< Statistics collection policy (THREAD_STATS_OFF / ADHOC / central), from utils::GetThreadStatColAbility()
	uint64 time[10];		//!< Rolling timestamps of process-wide summary samples (newest first)
	uint64 currentCPUTicks[10];	//!< Rolling process-wide summed CPU ticks matching @c time
	uint32 bitFieldSize;	//!< Size in bytes of the slot-occupancy bitfield that follows this header
	// Bitfield
	// Stats1, Stats2, ...
};


/*! @brief Minimal cooperative-shutdown base class for objects that run a worker thread.
    @ingroup Threading

    A subclass runs its loop while @c shouldContinue is true and sets
    @c isRunning around the loop body. stop() flips @c shouldContinue and then
    spin-waits (5 ms steps) for the loop to observe it and clear @c isRunning.
    This is cooperative: the thread is never forcibly killed by stop(); a loop
    that blocks indefinitely will simply cause stop() to time out.

    @warning The flags are plain bools with no atomics or memory barriers; the
             pattern relies on the platforms' strong-enough visibility for
             single-writer boolean flags. Do not extend it to carry data. */
class Runnable {
public:
	//! Initialise flags: not running, allowed to continue.
	Runnable() {threadID = 0; shouldContinue = true; isRunning = false;}
	//! Destructor requests a stop (with the default timeout) before destruction proceeds.
	virtual ~Runnable() {
		stop();
	}
	/*! @brief Ask the worker loop to finish and wait for it to do so.
	    @param timeout Maximum time to wait, in milliseconds (default 200).
	    @return true if the loop ended within the timeout; false if it was still
	            running when time ran out (a diagnostic is logged).
	    @note Safe to call multiple times and from any thread. */
	virtual bool stop(uint32 timeout = 200) {
		shouldContinue = false;
		int32 timeleft = (int32)timeout;
		while (isRunning) {
			utils::Sleep(5);
			if ( (timeleft -= 5) <= 0) {
				LogPrint(0,LOG_SYSTEM,0,"Thread id %u didn't terminate...\n", threadID);
				return false;
			}
		}
		return true;
	}

protected:
	uint32 threadID;		//!< ThreadManager slot ID of the worker thread (0 until known)
	bool shouldContinue;	//!< Loop-continuation flag; cleared by stop()
	bool isRunning;			//!< Set by the worker while its loop is active
};

/*! @brief Compute the address of thread slot @a id inside storage block @a data.
    @ingroup Threading
    Resolves the block layout header + bitfield + stats[] described in
    ThreadDataHeader. Must only be used while holding the manager mutex, since
    a concurrent resizeThreadStorage() may free the block. */
#define GETTHREADSTATS(data,id) (ThreadStats*)(data + sizeof(ThreadDataHeader) + ((ThreadDataHeader*)data)->bitFieldSize + id*sizeof(ThreadStats))

/*! @brief Singleton registry that creates, controls and profiles all CMSDK threads.
    @ingroup Threading

    All public functionality is exposed as static methods that lazily create
    the singleton on first use, so callers never need to construct one
    explicitly. Internally every operation takes the single manager mutex,
    serialising thread creation/termination and statistics access.

    The manager also runs its own monitoring thread (slot 0, see
    threadMonitoring()) which samples per-thread CPU usage about once per
    second and detects threads that have finished on their own.

    @warning ThreadManager hands out raw preemptive threads. Any state shared
             between them - including a one-shot crank function running
             concurrently for several trigger firings - must be protected by
             the caller (e.g. utils::Mutex); the manager only synchronises its
             own bookkeeping, never user data.
    @warning TerminateThread()/InterruptThread() kill threads asynchronously
             (Windows TerminateThread / pthread_cancel). A thread killed this
             way does not unwind: it can leak resources or leave locks held.
             Prefer cooperative shutdown (Runnable::stop()) whenever possible. */
class ThreadManager : public Runnable {
public:
	//! The process-wide instance; NULL until CreateThreadManager() (or any lazy static call) runs.
	static ThreadManager* Singleton;

	/*! @brief Create and initialise the singleton (including its monitoring thread).
	    @return true on success; false if initialisation failed (instance is deleted again).
	    @note Called automatically by every other static method; explicit use is optional. */
	static bool CreateThreadManager();
	/*! @brief Create a new native thread and start it immediately.
	    @param func  Entry function (THREAD_FUNCTION signature).
	    @param args  Opaque pointer handed to @a func; must outlive the thread.
	    @param newID Receives the manager-assigned slot ID of the new thread.
	    @param reqID Optional specific slot to (re)use; 0 = allocate the first free slot.
	    @return true if the OS thread was created and registered. */
	static bool CreateThread(THREAD_FUNCTION func, void* args, uint32& newID, uint32 reqID = 0);
	/*! @brief Suspend the thread at whatever point it is currently executing.
	    @param id Manager slot ID.
	    @return true if the OS suspend succeeded (status becomes THREAD_PAUSED).
	    @warning Suspending a thread that holds a lock can deadlock the process;
	             pause only threads whose code you control. Not supported by all
	             pthreads platforms. */
	static bool PauseThread(uint32 id);
	/*! @brief Resume a thread previously suspended with PauseThread().
	    @param id Manager slot ID.
	    @return true if the OS resume succeeded (status back to THREAD_RUNNING). */
	static bool ContinueThread(uint32 id);
	/*! @brief Kill the thread and restart it from its original entry function.
	    @param id Manager slot ID.
	    @return Currently always false (see implementation); the restart itself
	            is attempted regardless.
	    @warning Forcible kill + restart: no stack unwinding, no cleanup in the
	             old thread. Use only for genuinely stuck threads. */
	static bool InterruptThread(uint32 id);
	/*! @brief Forcibly terminate the thread and release its slot.
	    @param id Manager slot ID.
	    @return true once the slot is marked THREAD_TERMINATED.
	    @warning Asynchronous kill; the victim gets no chance to clean up. */
	static bool TerminateThread(uint32 id);
	/*! @brief Check whether the thread is still alive at the OS level.
	    @param id Manager slot ID.
	    @return true if the underlying OS thread is running; false if it exited,
	            was terminated, or the slot has no handle. */
	static bool IsThreadRunning(uint32 id);

	/*! @brief Sample and record CPU statistics for the calling thread itself.
	    @return true if a sample was taken and stored.
	    @note Needed on pthreads (THREAD_STATS_ADHOC policy), where one thread
	          cannot read another thread's CPU usage; each thread must call this
	          periodically to self-report. On Windows the central monitor
	          samples all threads and this is unnecessary. */
	static bool AddLocalThreadStats();
	/*! @brief Look up the manager slot ID of the calling thread.
	    @param id Receives the slot ID on success.
	    @return true if the calling thread's OS ID was found in the registry
	            (i.e. it was created through ThreadManager). */
	static bool GetLocalThreadID(uint32& id);

	/*! @brief Get a copy of the statistics record for the calling thread.
	    @return ThreadStats snapshot; @c created == 0 signals "not found". */
	static ThreadStats GetLocalThreadStats();
	/*! @brief Get a copy of the statistics record for a specific thread.
	    @param id Manager slot ID.
	    @return ThreadStats snapshot; @c created == 0 signals "not found". */
	static ThreadStats GetThreadStats(uint32 id);
	/*! @brief Snapshot the statistics of all live threads.
	    @param count Receives the number of records returned.
	    @return Newly allocated array of @a count ThreadStats, or NULL on failure.
	    @note Caller owns the array and must release it with @c delete[]. */
	static ThreadStats* GetAllThreadStats(uint32& count);

	/*! @brief Terminate all managed threads, then destroy the singleton.
	    @return true (also when no manager existed).
	    @warning Uses forcible termination for any thread still running; make
	             sure workers have been asked to stop first. */
	static bool Shutdown();

	/*! @brief Self-contained unit test (create/run/join worker threads, verify stats).
	    @return true on success. Registered with the UnitTestRunner as "threadmanager". */
	static bool UnitTest();

	//! Construct the manager and pre-allocate storage for 1024 thread slots. Prefer CreateThreadManager().
	ThreadManager();
	//! Destructor: shuts down all threads, frees the storage block and clears the singleton pointer.
	~ThreadManager();
	/*! @brief Second-phase init: registers this instance as the singleton and starts
	    the monitoring thread in slot 0.
	    @return true on success; on failure the caller should delete the instance. */
	bool init();
	/*! @brief Stop the monitoring loop and forcibly terminate all remaining threads.
	    @return true when all slots have been marked terminated. */
	bool shutdown();

	/*! @brief Body of the monitoring thread (slot 0).

	    Roughly once per second it either (a) under THREAD_STATS_ADHOC, rotates
	    the process-wide summary buffers and reaps threads that finished, or
	    (b) under central collection, samples every live thread's CPU ticks,
	    sums them into the header and rotates all rolling windows.
	    Runs until Runnable::stop() clears @c shouldContinue.
	    @return 0 on clean exit (or when stats are disabled), -1 if the mutex
	            could not be acquired. */
	int32 threadMonitoring();

protected:
	//! Instance-side worker for CreateThread(); requires and manages the mutex internally. @see CreateThread()
	bool createThread(THREAD_FUNCTION func, void* args, uint32& newID, uint32 reqID);
	//! Instance-side worker for PauseThread(). @see PauseThread()
	bool pauseThread(uint32 id);
	//! Instance-side worker for ContinueThread(). @see ContinueThread()
	bool continueThread(uint32 id);
	//! Instance-side worker for InterruptThread(). @see InterruptThread()
	bool interruptThread(uint32 id);
	//! Instance-side worker for TerminateThread(). @see TerminateThread()
	bool terminateThread(uint32 id);
	//! Instance-side worker for IsThreadRunning(). @see IsThreadRunning()
	bool isThreadRunning(uint32 id);

	//! Instance-side worker for AddLocalThreadStats() (pthreads self-reporting). @see AddLocalThreadStats()
	bool addLocalThreadStats();
	//! Instance-side worker for GetLocalThreadID(): linear scan of slots for the calling thread's OS ID.
	bool getLocalThreadID(uint32& id);

	//! Instance-side worker for GetThreadStats(): copies the slot under the mutex.
	ThreadStats getThreadStats(uint32 id);
	//! Instance-side worker for GetAllThreadStats(): allocates and fills a snapshot array (caller frees with delete[]).
	ThreadStats* getAllThreadStats(uint32& count);

	/*! @brief Grow the storage block to hold @a newCount slots.
	    @param newCount New slot capacity (createThread grows by 4x when full).
	    @return true on success.
	    @note Allocates a fresh block, copies header/bitfield/stats across and
	          frees the old one, so all raw ThreadStats pointers are invalidated;
	          callers must hold the mutex. */
	bool resizeThreadStorage(uint32 newCount);

	utils::Mutex mutex;		//!< Single lock serialising all registry and statistics access
	unsigned char* data;	//!< Contiguous storage block: ThreadDataHeader + bitfield + ThreadStats[]
};

//! Legacy standalone smoke test for ThreadManager (predates the UnitTestRunner framework).
bool TestThreadManager();

/*! @brief Entry function of the monitoring thread; delegates to ThreadManager::threadMonitoring().
    @ingroup Threading
    On POSIX (non-Darwin) it first unblocks SIGQUIT so the thread can be
    cancelled; on Windows no signal setup is needed. */
static THREAD_RET THREAD_FUNCTION_CALL ThreadMonitoring(void* arg);


} // namespace cmlabs

#endif //_THREADMANAGER_H_

