55 if (!threadManager->
init()) {
57 delete(threadManager);
215 mutex.setCancelSafe(
true);
259 if (!
mutex.enter()) {
279 bool anyRunning =
true;
283 for (uint32 n=0; n<header->
count; n++, stats++) {
296 for (uint32 n=0; n<header->
count; n++) {
456 if (pthread_equal(stats->
hThread, pthread_self())) {
547 if (pthread_equal(h, pthread_self())) {
603 if (!stats || !header)
635 if (!
mutex.enter()) {
653 for (uint32 n=0; n<header->
count; n++) {
655 allStats[p++] = *stats;
657 if (p >= count)
break;
668 unsigned char *newData = (
unsigned char*) malloc(size);
669 memset(newData, 0, size);
672 header->
count = newCount;
681 memset(newData+statsOffset, 0, 10*2*(
sizeof(uint64)));
686 memset(firstStat, 0, newCount*
sizeof(
ThreadStats));
692 memcpy(newData+statsOffset,
data+statsOffset, 10*2*(
sizeof(uint64)));
717 if ( (stats == NULL) || (stats->
created == 0) ) {
722 uint32 timeOffset =
sizeof(stats->
id) +
sizeof(stats->
created) +
sizeof(stats->
status)
724 uint32 cpuUsageOffset = timeOffset + 10 *
sizeof(uint64);
732 memcpy(stats+timeOffset, stats+timeOffset+
sizeof(uint64), 9*
sizeof(uint64));
734 memcpy(stats+cpuUsageOffset, stats+cpuUsageOffset+
sizeof(uint64), 9*
sizeof(uint64));
740 header->time[0] = stats->
time[0];
767 for (uint32 n=0; n<header->
count; n++) {
768 if (stats->
osID == osID) {
792 sigemptyset(&cancel);
793 sigaddset(&cancel, SIGQUIT);
794 pthread_sigmask(SIG_UNBLOCK, &cancel, NULL);
809 uint32 timeOffset =
sizeof(stats->
id) +
sizeof(stats->
created) +
sizeof(stats->
status)
811 uint32 cpuUsageOffset = timeOffset + 10 *
sizeof(uint64);
819 uint32 headerTimeOffset =
sizeof(header->size)+
sizeof(header->count)+
sizeof(header->activeCount)+
sizeof(header->statColPolicy);
820 uint32 headerCPUUsageOffset = headerTimeOffset + 10 *
sizeof(uint64);
823 uint32 interval = 1000000, n;
825 uint64 lastCalc = 0, t;
827 if ( (t =
GetTimeNow()) - lastCalc > interval ) {
830 if (!
mutex.enter()) {
842 for (n=0; n<header->count; n++) {
854 memcpy(header+headerTimeOffset, header+headerTimeOffset+
sizeof(uint64), 9*
sizeof(uint64));
856 memcpy(header+headerCPUUsageOffset, header+headerCPUUsageOffset+
sizeof(uint64), 9*
sizeof(uint64));
858 header->time[0] = header->currentCPUTicks[0] = 0;
864 for (n=0; n<header->count; n++) {
867 memcpy(stats+timeOffset, stats+timeOffset+
sizeof(uint64), 9*
sizeof(uint64));
869 memcpy(stats+cpuUsageOffset, stats+cpuUsageOffset+
sizeof(uint64), 9*
sizeof(uint64));
888 memcpy(header+headerTimeOffset, header+headerTimeOffset+
sizeof(uint64), 9*
sizeof(uint64));
890 memcpy(header+headerCPUUsageOffset, header+headerCPUUsageOffset+
sizeof(uint64), 9*
sizeof(uint64));
893 header->currentCPUTicks[0] = sumUsage;
917 uint32 finishedCount;
918 TMTestState() : total(0), finishedCount(0) {}
923static const uint64 TM_ITERATIONS_PER_THREAD = 200000ULL;
926 TMTestState* state = (TMTestState*)arg;
928 for (uint64 i = 0; i < TM_ITERATIONS_PER_THREAD; i++)
930 if (state->mutex.enter()) {
931 state->total += localSum;
932 state->finishedCount++;
933 state->mutex.leave();
952 unittest::fail(
"ThreadManager test: CreateThreadManager() failed");
956 const uint32 NUMTHREADS = 8;
958 uint32 ids[NUMTHREADS];
959 for (uint32 n = 0; n < NUMTHREADS; n++)
966 for (uint32 n = 0; n < NUMTHREADS; n++) {
968 unittest::fail(
"ThreadManager test: CreateThread() failed for worker %u", n);
975 if (started != NUMTHREADS) {
976 unittest::fail(
"ThreadManager test: started %u of %u threads", started, NUMTHREADS);
985 const uint32 TIMEOUTMS = 5000;
987 bool allFinished =
false;
988 while (waitedMs < TIMEOUTMS) {
990 if (state.mutex.
enter()) {
991 done = state.finishedCount;
994 if (done >= NUMTHREADS) {
1003 unittest::fail(
"ThreadManager test: only %u of %u workers finished within %ums",
1004 state.finishedCount, NUMTHREADS, TIMEOUTMS);
1011 uint64 expected = (uint64)NUMTHREADS * TM_ITERATIONS_PER_THREAD;
1012 if (state.total != expected) {
1013 unittest::fail(
"ThreadManager test: total work %llu != expected %llu",
1014 state.total, expected);
1018 if (state.finishedCount != NUMTHREADS) {
1020 state.finishedCount, NUMTHREADS);
1028 unittest::fail(
"ThreadManager test: GetThreadStats returned no creation time for id %u", ids[0]);
1035 unittest::metric(
"threads_per_sec", (
double)NUMTHREADS / us * 1e6,
"threads/s",
true);
1075 const uint32 CYCLES = 1300;
1076 uint32 created = 0, failedAt = 0;
1080 for (uint32 n = 0; n < CYCLES; n++) {
1096 unittest::fail(
"slot recycling: CreateThread() failed after %u lifetimes "
1097 "(highest slot id seen %u) - registry slots are not being released",
1102 if (created != CYCLES) {
1103 unittest::fail(
"slot recycling: completed %u of %u lifetimes", created, CYCLES);
1109 if (maxID >= CYCLES) {
1110 unittest::fail(
"slot recycling: highest slot id %u >= %u lifetimes - ids are "
1111 "not being recycled", maxID, CYCLES);
1115 unittest::detail(
"%u thread lifetimes, highest slot id %u", created, maxID);
1130 "Thread manager create/run/join of multiple worker threads",
"core");
1132 "Thread manager releases and reuses registry slots across >1024 lifetimes",
"core");
CMSDK time: µs-resolution 64-bit timestamps and the Time Mapping Constant (TMC).
Process-wide thread registry and lifecycle manager: the concurrency core of CMSDK.
Small, dependency-free unit test harness used by all CMSDK object tests.
Cross-platform utility toolbox for CMSDK: threading, synchronization, shared memory,...
#define thread_ret_val(ret)
#define THREAD_STATS_ADHOC
#define THREAD_TERMINATED
#define THREAD_FUNCTION_CALL
THREAD_RET(* THREAD_FUNCTION)(void *)
#define THREAD_INTERRUPTED
bool isRunning
Set by the worker while its loop is active.
virtual bool stop(uint32 timeout=200)
Ask the worker loop to finish and wait for it to do so.
bool shouldContinue
Loop-continuation flag; cleared by stop().
Singleton registry that creates, controls and profiles all CMSDK threads.
static bool UnitTestSlotRecycling()
Churn MORE thread lifetimes than the registry has slots, in one process.
bool createThread(THREAD_FUNCTION func, void *args, uint32 &newID, uint32 reqID)
Instance-side worker for CreateThread(); requires and manages the mutex internally.
bool resizeThreadStorage(uint32 newCount)
Grow the storage block to hold newCount slots.
bool addLocalThreadStats()
Instance-side worker for AddLocalThreadStats() (pthreads self-reporting).
static bool CreateThreadManager()
Create and initialise the singleton (including its monitoring thread).
bool pauseThread(uint32 id)
Instance-side worker for PauseThread().
bool getLocalThreadID(uint32 &id)
Instance-side worker for GetLocalThreadID(): linear scan of slots for the calling thread's OS ID.
static bool GetLocalThreadID(uint32 &id)
Look up the manager slot ID of the calling thread.
bool continueThread(uint32 id)
Instance-side worker for ContinueThread().
bool joinThread(uint32 id)
Instance-side worker for JoinThread().
ThreadManager()
Construct the manager and pre-allocate storage for 1024 thread slots. Prefer CreateThreadManager().
bool interruptThread(uint32 id)
Instance-side worker for InterruptThread().
ThreadStats * getAllThreadStats(uint32 &count)
Instance-side worker for GetAllThreadStats(): allocates and fills a snapshot array (caller frees with...
static bool InterruptThread(uint32 id)
Kill the thread and restart it from its original entry function.
static bool GetThreadHandle(uint32 id, ThreadHandle &out)
Copy out the OS-level handle of the thread in a manager slot.
~ThreadManager()
Destructor: shuts down all threads, frees the storage block and clears the singleton pointer.
bool getThreadHandle(uint32 id, ThreadHandle &out)
Instance-side worker for GetThreadHandle().
void finaliseSlot(ThreadStats *stats, ThreadDataHeader *header)
Mark a slot terminated and RELEASE it back to the allocation bitfield.
bool shutdown()
Stop the monitoring loop and forcibly terminate all remaining threads.
utils::Mutex mutex
Single lock serialising all registry and statistics access.
static ThreadStats GetLocalThreadStats()
Get a copy of the statistics record for the calling thread.
bool terminateThread(uint32 id)
Instance-side worker for TerminateThread().
static ThreadStats GetThreadStats(uint32 id)
Get a copy of the statistics record for a specific thread.
static bool CreateThread(THREAD_FUNCTION func, void *args, uint32 &newID, uint32 reqID=0)
Create a new native thread and start it immediately.
int32 threadMonitoring()
Body of the monitoring thread (slot 0).
static bool JoinThread(uint32 id)
Wait for a thread to have COMPLETELY finished, then release it - exactly once.
unsigned char * data
Contiguous storage block: ThreadDataHeader + bitfield + ThreadStats[].
ThreadStats getThreadStats(uint32 id)
Instance-side worker for GetThreadStats(): copies the slot under the mutex.
static ThreadManager * Singleton
The process-wide instance; NULL until CreateThreadManager() (or any lazy static call) runs.
static bool UnitTest()
Self-contained unit test (create/run/join worker threads, verify stats).
static bool IsThreadRunning(uint32 id)
Check whether the thread is still alive at the OS level.
bool init()
Second-phase init: registers this instance as the singleton and starts the monitoring thread in slot ...
static bool TerminateThread(uint32 id)
Forcibly terminate the thread and release its slot.
static bool AddLocalThreadStats()
Sample and record CPU statistics for the calling thread itself.
static bool Shutdown()
Terminate all managed threads, then destroy the singleton.
static ThreadStats * GetAllThreadStats(uint32 &count)
Snapshot the statistics of all live threads.
static bool PauseThread(uint32 id)
Suspend the thread at whatever point it is currently executing.
static bool ContinueThread(uint32 id)
Resume a thread previously suspended with PauseThread().
bool isThreadRunning(uint32 id)
Instance-side worker for IsThreadRunning().
static UnitTestRunner & instance()
Access the singleton (created on first use).
void registerTest(const char *name, UnitTestFunc func, const char *description="", const char *category="", bool inDefaultRun=true)
Register a test with the runner.
Recursive mutual-exclusion lock, optionally named for cross-process use.
bool leave()
Release the mutex.
bool enter()
Block until the mutex is acquired.
uint64 GetTimeNow()
Return the current absolute time (µs since year 0) according to the TMC.
std::string PrintTimeDifString(uint64 t, bool us=true, bool ms=true)
int32 GetTimeAgeMS(uint64 t)
Age of a timestamp relative to now, in milliseconds.
int64 GetTimeAge(uint64 t)
Age of a timestamp relative to now.
static THREAD_RET THREAD_FUNCTION_CALL ThreadMonitoring(void *arg)
Entry function of the monitoring thread; delegates to ThreadManager::threadMonitoring().
#define GETTHREADSTATS(data, id)
Compute the address of thread slot id inside storage block data.
uint32 GetThreadStatColAbility()
Report this platform's capability for per-thread CPU statistics collection.
bool WaitForThreadToFinish(ThreadHandle hThread, uint32 timeoutMS=0)
Join a thread.
bool Sleep(uint32 ms)
Suspend the calling thread.
bool PauseThread(ThreadHandle hThread)
Suspend a thread's execution.
bool IsThreadRunning(ThreadHandle hThread)
Check whether a thread is still alive.
bool CreateThread(THREAD_FUNCTION func, void *args, ThreadHandle &thread, uint32 &osID)
Start a new OS thread.
bool ReapThread(ThreadHandle &hThread)
bool TerminateThread(ThreadHandle hThread)
Forcibly kill a thread.
bool GetCurrentThreadUniqueID(uint32 &tid)
Get a process-unique id for the calling thread.
bool ContinueThread(ThreadHandle hThread)
Resume a thread paused with PauseThread().
bool GetFirstFreeBitLoc(const char *bitfield, uint32 bytesize, uint32 &loc)
Find the first 0 (free) bit.
bool GetCPUTicks(ThreadHandle hThread, uint64 &ticks)
Get accumulated CPU time of a specific thread.
bool SetBit(uint32 loc, bit value, char *bitfield, uint32 bytesize)
Set bit loc to value.
bool TryReapThread(ThreadHandle &hThread)
uint32 Calc32BitFieldSize(uint32 bitsize)
Compute the byte size needed for a bitfield of bitsize bits, rounded up to a 32-bit boundary.
void fail(const char *fmt,...)
Set an explanatory reason shown on the FAIL line.
void metric(const char *name, double value, const char *unit="", bool higherIsBetter=true)
Record a performance metric.
void detail(const char *fmt,...)
Verbose-only indented diagnostic line (shown only when verbose=1).
void progress(int percent, const char *action)
Report progress with a short description of the current action.
static bool TryBeginThreadMgrTeardown()
static volatile int32 g_threadMgrTearingDown
static void EndThreadMgrTeardown()
void Register_ThreadManager_Tests()
Bookkeeping record for a single managed thread.
uint32 status
Lifecycle state (THREAD_NONE/INIT/RUNNING/PAUSED/INTERRUPTED/TERMINATED).
ThreadHandle hThread
Native handle (HANDLE on Windows, pthread_t wrapper on POSIX).
void * args
Argument passed to func, retained for restart.
uint64 currentCPUTicks[10]
Rolling CPU-tick readings matching time (newest first).
uint64 time[10]
Rolling timestamps of the last 10 statistics samples (newest first).
uint32 id
ThreadManager-assigned slot ID (index into the stats array).
uint32 osID
Operating-system thread ID (as reported by the OS, not the slot ID).
THREAD_FUNCTION func
Entry function, retained so InterruptThread() can restart the thread.
uint64 created
Creation time (microseconds, GetTimeNow()); 0 = slot unused/invalid.