CMSDK 2.0.1
Cross-platform C++ base library and SDK for the Psyclone AIOS platform
Loading...
Searching...
No Matches
ThreadManager.cpp
Go to the documentation of this file.
1
15#include "ThreadManager.h"
16#include "PsyTime.h"
17#include "Utils.h"
18#include "UnitTestFramework.h"
19
20namespace cmlabs {
21
23
25 ThreadManager* threadManager = new ThreadManager();
26 if (!threadManager->init()) {
27 //fprintf(stderr, "ThreadManager init() failed...\n");
28 delete(threadManager);
29 return false;
30 }
31 return true;
32}
33
34// Static
35// Create a new thread and start it
36bool ThreadManager::CreateThread(THREAD_FUNCTION func, void* args, uint32& newID, uint32 reqID) {
39 return false;
40 return ThreadManager::Singleton->createThread(func, args, newID, reqID);
41}
42
43// Static
44// Pause the thread wherever it is now
48 return false;
49 return ThreadManager::Singleton->pauseThread(id);
50}
51
52// Static
53// Allow the thread to continue running
57 return false;
58 return ThreadManager::Singleton->continueThread(id);
59}
60
61// Static
62// Terminate the thread and restart it from its base location
66 return false;
67 return ThreadManager::Singleton->interruptThread(id);
68}
69
70// Static
71// Terminate and destroy the thread
75 return false;
76 return ThreadManager::Singleton->terminateThread(id);
77}
78
79
80// Static
81// Terminate and destroy the thread
85 return false;
86 return ThreadManager::Singleton->isThreadRunning(id);
87}
88
89
90// Static
91// Add thread stats for local thread - needed for pthreads
95 return false;
96 return ThreadManager::Singleton->addLocalThreadStats();
97}
98
99// Static
100// Add thread stats for local thread - needed for pthreads
103 if (!CreateThreadManager())
104 return false;
105 return ThreadManager::Singleton->getLocalThreadID(id);
106}
107
108
109// Static
110// Get the statistics for local thread
112 ThreadStats stats;
113 stats.created = 0;
115 if (!CreateThreadManager())
116 return stats;
117 uint32 id;
119 return stats;
120 return ThreadManager::Singleton->getThreadStats(id);
121}
122
123// Static
124// Get the statistics for a thread
126 ThreadStats stats;
127 stats.created = 0;
129 if (!CreateThreadManager())
130 return stats;
131 return ThreadManager::Singleton->getThreadStats(id);
132}
133
134// Static
135// Get the statistics for all threads
138 if (!CreateThreadManager())
139 return NULL;
140 return ThreadManager::Singleton->getAllThreadStats(count);
141}
142
143// Static
144// Shutdown and delete the ThreadManager
147 return true;
149 return true;
152 return (ThreadManager::Singleton == NULL);
153}
154
155
156
157
159 // Initially allocate space for 1024 threads
160 data = NULL;
162 shouldContinue = true;
163 isRunning = false;
164}
165
167 if (ThreadManager::Singleton == NULL)
168 return;
169 if (!mutex.enter())
170 return;
171 shutdown();
172 // Deallocate data
174 free(data);
175 data = NULL;
176 mutex.leave();
177}
178
180 if (!mutex.enter())
181 return false;
182 // Startup the root thread for Windows only
183 uint32 newID = 0;
185 if ((!createThread(ThreadMonitoring, NULL, newID, 0)) || (newID != 0)) {
186 mutex.leave();
187 return false;
188 }
190 header->activeCount = 1;
191 mutex.leave();
192 return true;
193}
194
196 stop();
197 if (!mutex.enter())
198 return false;
199 // Give running threads a chance to exit on their own before resorting to
200 // utils::TerminateThread(). Forcibly terminating a thread that is right now
201 // exiting naturally (worker loops poll their stop flags every <=1s) can kill
202 // it inside DLL_THREAD_DETACH / CRT teardown while it holds the loader or
203 // heap lock. That abandoned lock then blocks every NEWLY created thread
204 // before it reaches its thread function - the cause of the intermittent
205 // CMSDK processmemory/network test failures on Windows.
207 ThreadStats* stats;
208#ifdef WINDOWS
209 // The natural-exit wait is only needed on Windows, where the OS
210 // TerminateThread() can abandon the loader/heap lock. On POSIX,
211 // utils::TerminateThread() is pthread_cancel()+pthread_join(), which is
212 // safe to apply immediately (and waiting merely shifts where the
213 // cancellation lands, which destabilised the network tests on Linux).
214 uint64 waitStart = GetTimeNow();
215 bool anyRunning = true;
216 while (anyRunning && GetTimeAgeMS(waitStart) < 2500) {
217 anyRunning = false;
218 stats = GETTHREADSTATS(data,0);
219 for (uint32 n=0; n<header->count; n++, stats++) {
220 if (stats->status > THREAD_TERMINATED &&
221 !utils::TryReapThread(stats->hThread)) {
222 anyRunning = true;
223 break;
224 }
225 }
226 if (anyRunning)
227 utils::Sleep(50);
228 }
229#endif
230 // Terminate whatever is genuinely still running, mark the rest terminated
231 stats = GETTHREADSTATS(data,0);
232 for (uint32 n=0; n<header->count; n++) {
233 if (stats->status > THREAD_TERMINATED) {
234 terminateThread(stats->id);
235 }
236 stats += 1;
237 }
238 header->activeCount = 0;
239 mutex.leave();
240 return true;
241}
242
243
244
245
246// Create a new thread and start it
247bool ThreadManager::createThread(THREAD_FUNCTION func, void* args, uint32& newID, uint32 reqID) {
248// fprintf(stderr, "ThreadManager creating thread enter...\n");
249 if (!mutex.enter())
250 return false;
251// fprintf(stderr, "ThreadManager creating thread got mutex...\n");
253 if (header->activeCount == header->count) {
254 // we need to resize Thread storage
255 if (!resizeThreadStorage(header->count * 4)) {
256 mutex.leave();
257 return false;
258 }
259 header = (ThreadDataHeader*)data;
260 }
261
262// fprintf(stderr, "ThreadManager creating thread 1...\n");
263 ThreadStats* stats;
264 if (reqID == 0) {
265 // A bit cheeky as root = 0, but it works well for the root thread as this is the first one requested anyway.
266 if (!utils::GetFirstFreeBitLoc(((const char*)data+sizeof(ThreadDataHeader)), header->bitFieldSize, newID) || (newID >= header->count)) {
267 mutex.leave();
268 return false;
269 }
270 stats = GETTHREADSTATS(data, newID);
271 stats->id = newID;
272 }
273 else {
274 newID = reqID;
275 stats = GETTHREADSTATS(data, newID);
276 stats->id = newID;
277 }
278
279// fprintf(stderr, "ThreadManager creating thread 2...\n");
280 stats->created = GetTimeNow();
281 stats->status = THREAD_INIT;
282 // We assume the stats are 0 already
283 // Create the thread
284 if (!utils::CreateThread(func, args, stats->hThread, stats->osID)) {
285 newID = 0;
286 stats->created = 0;
287 stats->status = THREAD_NONE;
288 mutex.leave();
289 return false;
290 }
291
292 // printf("Create thread %u = %u\n", stats->id, (uint32)stats->hThread);
293 utils::SetBit(stats->id, BITOCCUPIED, ((char*)data+sizeof(ThreadDataHeader)), header->bitFieldSize);
294 stats->func = func;
295 stats->args = args;
296 stats->status = THREAD_RUNNING;
297 header->activeCount++;
298 //fprintf(stderr, "ThreadManager creating thread ID %u, trace:\n%s", stats->osID);
299 //fprintf(stderr, "ThreadManager creating thread ID %u, trace:\n%s", stats->osID, utils::PrintProgramTrace(3, 6).c_str());
300 mutex.leave();
301// fprintf(stderr, "ThreadManager creating thread 4...\n");
302 return true;
303}
304
305// Pause the thread wherever it is now
307 if (!mutex.enter())
308 return false;
309 ThreadStats* stats = GETTHREADSTATS(data, id);
310 // Pause the thread
311 if (!utils::PauseThread(stats->hThread)) {
312 mutex.leave();
313 return false;
314 }
315 stats->status = THREAD_PAUSED;
316 mutex.leave();
317 return true;
318}
319
320// Allow the thread to continue running
322 if (!mutex.enter())
323 return false;
324 ThreadStats* stats = GETTHREADSTATS(data, id);
325 // Continue the thread
326 if (!utils::ContinueThread(stats->hThread)) {
327 mutex.leave();
328 return false;
329 }
330 stats->status = THREAD_RUNNING;
331 mutex.leave();
332 return true;
333}
334
335// Terminate the thread and restart it from its base location
337 if (!mutex.enter())
338 return false;
339 ThreadStats* stats = GETTHREADSTATS(data, id);
340 stats->status = THREAD_INTERRUPTED;
341 // Terminate the thread
342 // Terminate the thread
343 if (!utils::TerminateThread(stats->hThread)) {
344 mutex.leave();
345 return false;
346 }
348 header->activeCount--; // it will be increased again in createThread
349 // Create the thread again
350 uint32 newID = 0;
351 if (!createThread(stats->func, stats->args, newID, id)) {
352 stats->status = THREAD_TERMINATED;
353 stats->func = NULL;
354 stats->created = 0;
355 header->activeCount--;
356 mutex.leave();
357 return false;
358 }
359 mutex.leave();
360 return false;
361}
362
363// Terminate and destroy the thread
365 if (!mutex.enter())
366 return false;
368 ThreadStats* stats = GETTHREADSTATS(data, id);
369 // Only force-terminate a thread that is actually still running. Killing a
370 // thread that already exited (or is mid-exit) can abandon the loader/heap
371 // lock and silently break all subsequent thread creation (see shutdown()).
372 if (!utils::TryReapThread(stats->hThread)) {
373#ifdef WINDOWS
374 // brief grace period for threads that are just about to exit; killing a
375 // thread inside its exit path can abandon the loader/heap lock (Windows)
376 uint64 graceStart = GetTimeNow();
377 while (!utils::TryReapThread(stats->hThread) && GetTimeAgeMS(graceStart) < 250)
378 utils::Sleep(25);
379#endif
380 if (stats->hThread) {
381 utils::TerminateThread(stats->hThread); // cancels+joins (POSIX) / terminates+closes (Windows)
382 stats->hThread = 0;
383 }
384 }
385 //if (utils::IsThreadRunning(stats->hThread)) {
386 //if (!utils::TerminateThread(stats->hThread)) {
387 // mutex.leave();
388 // return false;
389 //}
390 //}
391 stats->status = THREAD_TERMINATED;
392 stats->func = NULL;
393 stats->created = 0;
394 header->activeCount--;
395 mutex.leave();
396 return true;
397}
398
399
400// Has the thread terminated or exited
402 if (!mutex.enter())
403 return false;
405 ThreadStats* stats = GETTHREADSTATS(data, id);
406 if (stats->hThread == 0) {
407 mutex.leave(); // was leaking the manager mutex on this early return
408 return false;
409 }
410 bool res = utils::IsThreadRunning(stats->hThread);
411 mutex.leave();
412 return res;
413}
414
415
416// Get the statistics for a thread
418 ThreadStats stats;
419 stats.created = 0;
420 if (!mutex.enter()) {
421 mutex.leave();
422 return stats;
423 }
424 stats = *GETTHREADSTATS(data, id);
425 mutex.leave();
426 return stats;
427}
428
429// Get the statistics for all threads
431 if (!mutex.enter())
432 return NULL;
434 count = header->activeCount;
435 ThreadStats* allStats = new ThreadStats[count];
436 uint32 p=0;
437 ThreadStats* stats = GETTHREADSTATS(data,0);
438 for (uint32 n=0; n<header->count; n++) {
439 if (stats->status > THREAD_TERMINATED)
440 allStats[p++] = *stats;
441 stats += 1;
442 if (p >= count) break;
443 }
444 mutex.leave();
445 return allStats;
446}
447
448// Resize the ThreadStats storage to be able to contain more threads
450
451 uint32 bitFieldSize = utils::Calc32BitFieldSize(newCount);
452 uint32 size = sizeof(ThreadDataHeader) + bitFieldSize + newCount*sizeof(ThreadStats);
453 unsigned char *newData = (unsigned char*) malloc(size);
454 memset(newData, 0, size);
455 ThreadDataHeader* header = (ThreadDataHeader*) newData;
456 header->bitFieldSize = bitFieldSize;
457 header->count = newCount;
458 header->size = size;
460
461 uint32 statsOffset = sizeof(header->size)+sizeof(header->count)+sizeof(header->activeCount)+sizeof(header->statColPolicy);
462
463 if (data == NULL) {
464 header->activeCount = 0;
465 // Set summary stats to 0
466 memset(newData+statsOffset, 0, 10*2*(sizeof(uint64)));
467 // Set the bitfield to unused
468 memset(newData+sizeof(ThreadDataHeader), 255, bitFieldSize);
469 // Set all thread stats to 0
470 ThreadStats* firstStat = GETTHREADSTATS(newData,0);
471 memset(firstStat, 0, newCount*sizeof(ThreadStats));
472 }
473 else {
474 ThreadDataHeader* oldHeader = (ThreadDataHeader*) data;
475 header->activeCount = oldHeader->activeCount;
476 // Copy old summary stats
477 memcpy(newData+statsOffset, data+statsOffset, 10*2*(sizeof(uint64)));
478 // Initially, set the bitfield to unused
479 memset(newData+sizeof(ThreadDataHeader), 255, bitFieldSize);
480 // Copy old bitfield
481 memcpy(newData+sizeof(ThreadDataHeader), data+sizeof(ThreadDataHeader), oldHeader->bitFieldSize);
482 ThreadStats* firstStat = GETTHREADSTATS(newData,0);
483 ThreadStats* oldFirstStat = GETTHREADSTATS(data,0);
484 memcpy(firstStat, oldFirstStat, oldHeader->count*sizeof(ThreadStats));
485 free(data);
486 }
487 data = newData;
488 return true;
489}
490
491// Add thread stats for local thread - needed for pthreads
493 if (!mutex.enter())
494 return false;
495
496 uint32 id = 0;
497 if (!getLocalThreadID(id)) {
498 mutex.leave();
499 return false;
500 }
501 ThreadStats* stats = GETTHREADSTATS(data,id);
502 if ( (stats == NULL) || (stats->created == 0) ) {
503 mutex.leave();
504 return false;
505 }
506
507 uint32 timeOffset = sizeof(stats->id) + sizeof(stats->created) + sizeof(stats->status)
508 + sizeof(stats->hThread) + sizeof(stats->func) + sizeof(stats->args);
509 uint32 cpuUsageOffset = timeOffset + 10 * sizeof(uint64);
510
511 ThreadDataHeader* header = NULL;
512
513// uint32 headerTimeOffset = sizeof(header->size)+sizeof(header->count)+sizeof(header->activeCount)+sizeof(header->statColPolicy);
514// uint32 headerCPUUsageOffset = headerTimeOffset + 10 * sizeof(uint64);
515
516 // Shift the previously stored values Time
517 memcpy(stats+timeOffset, stats+timeOffset+sizeof(uint64), 9*sizeof(uint64));
518 // CPU Usage
519 memcpy(stats+cpuUsageOffset, stats+cpuUsageOffset+sizeof(uint64), 9*sizeof(uint64));
520 // Read current stats from the thread
521 if (utils::GetCPUTicks(stats->currentCPUTicks[0])) {
522 stats->time[0] = GetTimeNow();
523 // add these to the header stat sums
524 header = (ThreadDataHeader*) data;
525 header->time[0] = stats->time[0];
526 header->currentCPUTicks[0] += stats->currentCPUTicks[0];
527 // but do not rotate
528 }
529 else {
530 stats->time[0] = stats->currentCPUTicks[0] = 0;
531 mutex.leave();
532 return false;
533 }
534
535 mutex.leave();
536 return true;
537}
538
539// Get the local thread ID
541 if (!mutex.enter())
542 return false;
543
544 uint32 osID;
546 mutex.leave();
547 return false;
548 }
550
551 ThreadStats* stats = GETTHREADSTATS(data,0);
552 for (uint32 n=0; n<header->count; n++) {
553 if (stats->osID == osID) {
554 id = stats->id;
555 mutex.leave();
556 return true;
557 }
558 stats += 1;
559 }
560
561 // for pthreads one has to delete the handle afterwards
562 //#ifndef WINDOWS
563 // delete(hThread);
564 //#endif
565 mutex.leave();
566 return false;
567}
568
569
570// Root Thread, for Windows only until pthreads support getrusage from other threads
572
573 #ifdef WINDOWS
574 #else
575 #ifndef Darwin
576 sigset_t cancel;
577 sigemptyset(&cancel);
578 sigaddset(&cancel, SIGQUIT);
579 pthread_sigmask(SIG_UNBLOCK, &cancel, NULL);
580 #endif
581 #endif
582
584 return 0;
585
586 thread_ret_val(ThreadManager::Singleton->threadMonitoring());
587}
588
590
591 isRunning = true;
592
593 ThreadStats* stats = NULL;
594 uint32 timeOffset = sizeof(stats->id) + sizeof(stats->created) + sizeof(stats->status)
595 + sizeof(stats->hThread) + sizeof(stats->func) + sizeof(stats->args);
596 uint32 cpuUsageOffset = timeOffset + 10 * sizeof(uint64);
597
599 if (header->statColPolicy == THREAD_STATS_OFF) {
600 isRunning = false;
601 return 0; // do not continue as nothing is done anyway
602 }
603
604 uint32 headerTimeOffset = sizeof(header->size)+sizeof(header->count)+sizeof(header->activeCount)+sizeof(header->statColPolicy);
605 uint32 headerCPUUsageOffset = headerTimeOffset + 10 * sizeof(uint64);
606
607 uint64 sumUsage;
608 uint32 interval = 1000000, n;
609
610 uint64 lastCalc = 0, t;
611 while (shouldContinue) {
612 if ( (t = GetTimeNow()) - lastCalc > interval ) {
613 lastCalc = t;
614
615 if (!mutex.enter()) {
616 isRunning = false;
617 return -1;
618 }
619
620 header = (ThreadDataHeader*) data;
621 stats = GETTHREADSTATS(data,0);
622
623 if (header->statColPolicy == THREAD_STATS_ADHOC) {
624 // Threads have to call the stat gathering themselves
625 // so we just rotate the global stats
626
627 for (n=0; n<header->count; n++) {
628 if (stats->status > THREAD_TERMINATED) {
629 // check if it is still actually running
630 if (utils::TryReapThread(stats->hThread)) { // reaps exactly once; zeroes handle
631 stats->status = THREAD_TERMINATED;
632 LogPrint(0, LOG_PROCESS, 2, "Thread finished: ID %u (OSID %u) age: %s", stats->id, stats->osID, PrintTimeDifString((uint32)GetTimeAge(stats->created)).c_str());
633 }
634 }
635 stats += 1;
636 }
637
638 // Shift the previously stored values Time
639 memcpy(header+headerTimeOffset, header+headerTimeOffset+sizeof(uint64), 9*sizeof(uint64));
640 // CPU Usage
641 memcpy(header+headerCPUUsageOffset, header+headerCPUUsageOffset+sizeof(uint64), 9*sizeof(uint64));
642
643 header->time[0] = header->currentCPUTicks[0] = 0;
644 }
645 else {
646 // We have to gather the stats, sum them up and rotate all buffers
647 sumUsage = 0;
648
649 for (n=0; n<header->count; n++) {
650 if (stats->status > THREAD_TERMINATED) {
651 // Shift the previously stored values Time
652 memcpy(stats+timeOffset, stats+timeOffset+sizeof(uint64), 9*sizeof(uint64));
653 // CPU Usage
654 memcpy(stats+cpuUsageOffset, stats+cpuUsageOffset+sizeof(uint64), 9*sizeof(uint64));
655 // Read current stats from the thread
656 if (utils::GetCPUTicks(stats->hThread, stats->currentCPUTicks[0])) {
657 stats->time[0] = GetTimeNow();
658 sumUsage += stats->currentCPUTicks[0];
659 }
660 else {
661 stats->time[0] = stats->currentCPUTicks[0] = 0;
662 }
663 // check if it is still actually running
664 if (utils::TryReapThread(stats->hThread)) { // reaps exactly once; zeroes handle
665 stats->status = THREAD_TERMINATED;
666 LogPrint(0, LOG_PROCESS, 2, "Thread finished: ID %u (OSID %u) age: %s", stats->id, stats->osID, PrintTimeDifString((uint32)GetTimeAge(stats->created)).c_str());
667 }
668 }
669 stats += 1;
670 }
671
672 // Shift the previously stored values Time
673 memcpy(header+headerTimeOffset, header+headerTimeOffset+sizeof(uint64), 9*sizeof(uint64));
674 // CPU Usage
675 memcpy(header+headerCPUUsageOffset, header+headerCPUUsageOffset+sizeof(uint64), 9*sizeof(uint64));
676
677 header->time[0] = GetTimeNow();
678 header->currentCPUTicks[0] = sumUsage;
679 }
680
681 mutex.leave();
682 }
683
684 utils::Sleep(100);
685 }
686
687 isRunning = false;
688 return 0;
689}
690
691
692// ################# Unit test #################
693
694namespace {
695
696// Shared state for the ThreadManager unit test. Each worker thread does a
697// bounded amount of work (a fixed number of increments), accumulates its
698// result into a shared total under a mutex, then marks itself finished.
699struct TMTestState {
700 utils::Mutex mutex;
701 uint64 total; // sum of all per-thread work, protected by mutex
702 uint32 finishedCount; // number of worker threads that ran to completion
703 TMTestState() : total(0), finishedCount(0) {}
704};
705
706// Each worker performs this many increments. Kept modest so the whole test
707// finishes in well under a second, but large enough to be measurable.
708static const uint64 TM_ITERATIONS_PER_THREAD = 200000ULL;
709
710static THREAD_RET THREAD_FUNCTION_CALL TMTestWorker(THREAD_ARG arg) {
711 TMTestState* state = (TMTestState*)arg;
712 uint64 localSum = 0;
713 for (uint64 i = 0; i < TM_ITERATIONS_PER_THREAD; i++)
714 localSum += 1;
715 if (state->mutex.enter()) {
716 state->total += localSum;
717 state->finishedCount++;
718 state->mutex.leave();
719 }
721}
722
723} // anonymous namespace
724
726
727 // 1. Bring up the ThreadManager singleton.
728 unittest::progress(5, "create thread manager");
730 unittest::fail("ThreadManager test: CreateThreadManager() failed");
731 return false;
732 }
733
734 const uint32 NUMTHREADS = 8;
735 TMTestState state;
736 uint32 ids[NUMTHREADS];
737 for (uint32 n = 0; n < NUMTHREADS; n++)
738 ids[n] = 0;
739
740 // 2. Start the worker threads.
741 unittest::progress(20, "start worker threads");
742 uint64 t0 = GetTimeNow();
743 uint32 started = 0;
744 for (uint32 n = 0; n < NUMTHREADS; n++) {
745 if (!ThreadManager::CreateThread(TMTestWorker, &state, ids[n])) {
746 unittest::fail("ThreadManager test: CreateThread() failed for worker %u", n);
748 return false;
749 }
750 unittest::detail("started worker %u with id %u", n, ids[n]);
751 started++;
752 }
753 if (started != NUMTHREADS) {
754 unittest::fail("ThreadManager test: started %u of %u threads", started, NUMTHREADS);
756 return false;
757 }
758
759 // 3. Wait (bounded) for every worker to finish its work. We poll the shared
760 // finished counter and also the OS-level running state, with a hard
761 // timeout so the test can never hang.
762 unittest::progress(50, "wait for workers");
763 const uint32 TIMEOUTMS = 5000;
764 uint32 waitedMs = 0;
765 bool allFinished = false;
766 while (waitedMs < TIMEOUTMS) {
767 uint32 done = 0;
768 if (state.mutex.enter()) {
769 done = state.finishedCount;
770 state.mutex.leave();
771 }
772 if (done >= NUMTHREADS) {
773 allFinished = true;
774 break;
775 }
776 utils::Sleep(5);
777 waitedMs += 5;
778 }
779
780 if (!allFinished) {
781 unittest::fail("ThreadManager test: only %u of %u workers finished within %ums",
782 state.finishedCount, NUMTHREADS, TIMEOUTMS);
784 return false;
785 }
786
787 // 4. Verify the threads actually all ran and produced the expected total.
788 unittest::progress(75, "verify results");
789 uint64 expected = (uint64)NUMTHREADS * TM_ITERATIONS_PER_THREAD;
790 if (state.total != expected) {
791 unittest::fail("ThreadManager test: total work %llu != expected %llu",
792 state.total, expected);
794 return false;
795 }
796 if (state.finishedCount != NUMTHREADS) {
797 unittest::fail("ThreadManager test: finishedCount %u != %u",
798 state.finishedCount, NUMTHREADS);
800 return false;
801 }
802
803 // 5. Sanity-check the statistics API for one of the threads.
805 if (stats.created == 0) {
806 unittest::fail("ThreadManager test: GetThreadStats returned no creation time for id %u", ids[0]);
808 return false;
809 }
810
811 double us = (double)(GetTimeNow() - t0);
812 if (us > 0.0) {
813 unittest::metric("threads_per_sec", (double)NUMTHREADS / us * 1e6, "threads/s", true);
814 unittest::metric("total_run_time", us, "us", false);
815 }
816
817 // 6. Tear everything down cleanly. No threads remain after this point.
818 unittest::progress(90, "shutdown");
820 unittest::fail("ThreadManager test: Shutdown() failed");
821 return false;
822 }
823
824 unittest::progress(100, "done");
825 return true;
826}
827
830 "Thread manager create/run/join of multiple worker threads", "core");
831}
832
833} // namespace cmlabs
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.
#define BITOCCUPIED
Definition Types.h:34
Small, dependency-free unit test harness used by all CMSDK object tests.
Cross-platform utility toolbox for CMSDK: threading, synchronization, shared memory,...
#define THREAD_NONE
Definition Utils.h:93
#define thread_ret_val(ret)
Definition Utils.h:131
#define THREAD_STATS_ADHOC
Definition Utils.h:102
#define THREAD_STATS_OFF
Definition Utils.h:100
#define LOG_PROCESS
Definition Utils.h:200
#define THREAD_RET
Definition Utils.h:127
#define THREAD_TERMINATED
Definition Utils.h:94
#define THREAD_FUNCTION_CALL
Definition Utils.h:129
#define LogPrint
Definition Utils.h:313
#define THREAD_PAUSED
Definition Utils.h:97
#define THREAD_RUNNING
Definition Utils.h:96
THREAD_RET(* THREAD_FUNCTION)(void *)
Definition Utils.h:128
#define THREAD_INTERRUPTED
Definition Utils.h:98
#define THREAD_INIT
Definition Utils.h:95
#define THREAD_ARG
Definition Utils.h:130
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.
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().
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.
~ThreadManager()
Destructor: shuts down all threads, frees the storage block and clears the singleton pointer.
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).
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.
Definition Utils.h:463
bool leave()
Release the mutex.
Definition Utils.cpp:1204
bool enter()
Block until the mutex is acquired.
Definition Utils.cpp:1059
uint64 GetTimeNow()
Return the current absolute time (µs since year 0) according to the TMC.
Definition PsyTime.cpp:69
std::string PrintTimeDifString(uint64 t, bool us=true, bool ms=true)
Definition PsyTime.cpp:722
int32 GetTimeAgeMS(uint64 t)
Age of a timestamp relative to now, in milliseconds.
Definition PsyTime.cpp:35
int64 GetTimeAge(uint64 t)
Age of a timestamp relative to now.
Definition PsyTime.cpp:25
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.
Definition Utils.cpp:3198
bool Sleep(uint32 ms)
Suspend the calling thread.
Definition Utils.cpp:2830
bool PauseThread(ThreadHandle hThread)
Suspend a thread's execution.
Definition Utils.cpp:3098
bool IsThreadRunning(ThreadHandle hThread)
Check whether a thread is still alive.
Definition Utils.cpp:3168
bool CreateThread(THREAD_FUNCTION func, void *args, ThreadHandle &thread, uint32 &osID)
Start a new OS thread.
Definition Utils.cpp:2896
bool TerminateThread(ThreadHandle hThread)
Forcibly kill a thread.
Definition Utils.cpp:3068
bool GetCurrentThreadUniqueID(uint32 &tid)
Get a process-unique id for the calling thread.
Definition Utils.cpp:3118
bool ContinueThread(ThreadHandle hThread)
Resume a thread paused with PauseThread().
Definition Utils.cpp:3107
bool GetFirstFreeBitLoc(const char *bitfield, uint32 bytesize, uint32 &loc)
Find the first 0 (free) bit.
Definition Utils.cpp:2473
bool GetCPUTicks(ThreadHandle hThread, uint64 &ticks)
Get accumulated CPU time of a specific thread.
Definition Utils.cpp:3210
bool SetBit(uint32 loc, bit value, char *bitfield, uint32 bytesize)
Set bit loc to value.
Definition Utils.cpp:2597
bool TryReapThread(ThreadHandle &hThread)
Definition Utils.cpp:2992
uint32 Calc32BitFieldSize(uint32 bitsize)
Compute the byte size needed for a bitfield of bitsize bits, rounded up to a 32-bit boundary.
Definition Utils.cpp:2463
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.
void Register_ThreadManager_Tests()
Header at the start of the ThreadManager's contiguous storage block.
uint32 size
Total size of the storage block in bytes.
uint32 statColPolicy
Statistics collection policy (THREAD_STATS_OFF / ADHOC / central), from utils::GetThreadStatColAbilit...
uint32 count
Capacity: number of ThreadStats slots allocated.
uint32 bitFieldSize
Size in bytes of the slot-occupancy bitfield that follows this header.
uint32 activeCount
Number of slots currently holding a live (non-terminated) thread.
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.