SuperTinyKernel™ RTOS 1.05.3
Lightweight, high-performance, deterministic, bare-metal C++ RTOS for resource-constrained embedded systems. MIT Open Source License.
Loading...
Searching...
No Matches
test_condvar.cpp
Go to the documentation of this file.
1/*
2 * SuperTinyKernel(TM) RTOS: Lightweight High-Performance Deterministic C++ RTOS for Embedded Systems.
3 *
4 * Source: https://github.com/SuperTinyKernel-RTOS
5 *
6 * Copyright (c) 2022-2026 Neutron Code Limited <stk@neutroncode.com>. All Rights Reserved.
7 * License: MIT License, see LICENSE for a full text.
8 */
9
10#include <stk_config.h>
11#include <stk.h>
12#include <sync/stk_sync_mutex.h>
13#include <sync/stk_sync_cv.h>
14#include <assert.h>
15#include <string.h>
16
17#include "stktest_context.h"
18
19using namespace stk;
20using namespace stk::test;
21
23
24#define _STK_CV_TEST_TASKS_MAX 5
25#define _STK_CV_TEST_TIMEOUT 300
26#define _STK_CV_TEST_SHORT_SLEEP 10
27#define _STK_CV_TEST_LONG_SLEEP 100
28#ifdef __ARM_ARCH_6M__
29#define _STK_CV_STACK_SIZE 128 // ARM Cortex-M0
30#define STK_TASK
31#else
32#define _STK_CV_STACK_SIZE 256
33#define STK_TASK static
34#endif
35
36#ifndef _NEW
37inline void *operator new(std::size_t, void *ptr) noexcept { return ptr; }
38inline void operator delete(void *, void *) noexcept { /* nothing for placement delete */ }
39#endif
40
41namespace stk {
42namespace test {
43
47namespace condvar {
48
49// Test results storage
50static volatile int32_t g_TestResult = 0;
51static volatile int32_t g_SharedCounter = 0;
52static volatile int32_t g_AcquisitionOrder[_STK_CV_TEST_TASKS_MAX] = {0};
53static volatile int32_t g_OrderIndex = 0;
54static volatile bool g_TestComplete = false;
55static volatile int32_t g_InstancesDone = 0;
56
57// Kernel (ConditionVariable requires KERNEL_SYNC)
59
60// Test primitives (re-constructed per test via ResetTestState)
63
72template <EAccessMode _AccessMode>
73class NotifyOneWakesTask : public Task<_STK_CV_STACK_SIZE, _AccessMode>
74{
75 uint8_t m_task_id;
76 int32_t m_iterations;
77
78public:
79 NotifyOneWakesTask(uint8_t task_id, int32_t iterations) : m_task_id(task_id), m_iterations(iterations)
80 {}
81
82private:
83 void Run()
84 {
85 if (m_task_id == 0)
86 {
87 // Producer: let all consumers block first, then fire one notification per iteration
89
90 for (int32_t i = 0; i < m_iterations; ++i)
91 {
92 g_TestMutex.Lock();
93 g_TestCond.NotifyOne();
94 g_TestMutex.Unlock();
95
96 stk::Delay(1); // pace so each notification wakes exactly one blocked consumer
97 }
98
100
101 printf("notify-one wakes: counter=%d (expected %d)\n",
102 (int)g_SharedCounter, (int)m_iterations);
103
105 g_TestResult = 1;
106 }
107 else
108 {
109 // Consumers: loop waiting; each successful wake increments the counter
110 for (int32_t i = 0; i < m_iterations; ++i)
111 {
112 g_TestMutex.Lock();
115 g_TestMutex.Unlock();
116 }
117 }
118 }
119};
120
127template <EAccessMode _AccessMode>
128class NotifyAllWakesTask : public Task<_STK_CV_STACK_SIZE, _AccessMode>
129{
130 uint8_t m_task_id;
131
132public:
133 NotifyAllWakesTask(uint8_t task_id, int32_t) : m_task_id(task_id)
134 {}
135
136private:
137 void Run()
138 {
139 if (m_task_id == 0)
140 {
141 // Producer: let all 4 consumers reach Wait() before signaling
143
144 g_TestMutex.Lock();
145 g_TestCond.NotifyAll();
146 g_TestMutex.Unlock();
147
149
150 int32_t expected = _STK_CV_TEST_TASKS_MAX - 1;
151
152 printf("notify-all wakes: counter=%d (expected %d)\n",
153 (int)g_SharedCounter, (int)expected);
154
155 if (g_SharedCounter == expected)
156 g_TestResult = 1;
157 }
158 else
159 {
160 // All consumer tasks block and must all be released by the single NotifyAll()
161 g_TestMutex.Lock();
164 g_TestMutex.Unlock();
165 }
166 }
167};
168
175template <EAccessMode _AccessMode>
176class TimeoutExpiresTask : public Task<_STK_CV_STACK_SIZE, _AccessMode>
177{
178 uint8_t m_task_id;
179
180public:
181 TimeoutExpiresTask(uint8_t task_id, int32_t) : m_task_id(task_id)
182 {}
183
184private:
185 void Run()
186 {
187 if (m_task_id == 1)
188 {
189 // Wait with a short timeout; no producer fires, so it must expire
191
192 g_TestMutex.Lock();
193 int64_t start = GetTimeNowMs();
194 bool woken = g_TestCond.Wait(g_TestMutex, 50);
195 int64_t elapsed = GetTimeNowMs() - start;
196 g_TestMutex.Unlock();
197
198 // Must return false (timeout), and elapsed must be within the 50-tick window
199 if (!woken && elapsed >= 45 && elapsed <= 65)
200 ++g_SharedCounter; // 1: timeout returned false in correct time
201 }
202 else
203 if (m_task_id == 2)
204 {
205 // Wait with generous timeout; producer fires after task 1's timeout expires
206 stk::Sleep(120); // well past task 1's 50-tick timeout
207
208 g_TestMutex.Lock();
209 bool woken = g_TestCond.Wait(g_TestMutex, _STK_CV_TEST_TIMEOUT);
210 g_TestMutex.Unlock();
211
212 if (woken)
213 ++g_SharedCounter; // 2: correctly woken by producer
214 }
215 else
216 if (m_task_id == 0)
217 {
218 // Producer: fires after both task 1 timeout and task 2 block
219 stk::Sleep(130);
220
221 g_TestMutex.Lock();
222 g_TestCond.NotifyOne();
223 g_TestMutex.Unlock();
224
226
227 printf("timeout expires: counter=%d (expected 2)\n", (int)g_SharedCounter);
228
229 if (g_SharedCounter == 2)
230 g_TestResult = 1;
231 }
232 }
233};
234
243template <EAccessMode _AccessMode>
244class MutexReacquiredTask : public Task<_STK_CV_STACK_SIZE, _AccessMode>
245{
246 uint8_t m_task_id;
247
248public:
249 MutexReacquiredTask(uint8_t task_id, int32_t) : m_task_id(task_id)
250 {}
251
252private:
253 void Run()
254 {
255 if (m_task_id == 1)
256 {
257 // Consumer (signaled path): Wait() must re-acquire mutex on wake
258 g_TestMutex.Lock();
259
260 bool woken = g_TestCond.Wait(g_TestMutex, _STK_CV_TEST_TIMEOUT);
261
262 // If mutex was correctly re-acquired this Unlock() will not assert
263 if (woken && g_SharedCounter == 1)
264 ++g_SharedCounter; // 2: mutex re-acquired after signaled wake
265 g_TestMutex.Unlock();
266 }
267 else
268 if (m_task_id == 2)
269 {
270 // Consumer (timeout path): Wait() must also re-acquire mutex on timeout
271 g_TestMutex.Lock();
272
273 bool woken = g_TestCond.Wait(g_TestMutex, 50);
274
275 // If mutex was correctly re-acquired this Unlock() will not assert
276 if (!woken)
277 ++g_SharedCounter; // 3: mutex re-acquired after timeout
278 g_TestMutex.Unlock();
279 }
280 else
281 if (m_task_id == 0)
282 {
283 // Producer: wait for consumer 1 to enter Wait() then prove the mutex is free
285
286 g_TestMutex.Lock(); // must succeed: consumer released it inside Wait()
287 ++g_SharedCounter; // 1: mutex was free while consumer waited
288 g_TestCond.NotifyOne();
289 g_TestMutex.Unlock();
290
292
293 printf("mutex reacquired: counter=%d (expected 3)\n", (int)g_SharedCounter);
294
295 if (g_SharedCounter == 3)
296 g_TestResult = 1;
297 }
298 }
299};
300
309template <EAccessMode _AccessMode>
310class PredicateLoopTask : public Task<_STK_CV_STACK_SIZE, _AccessMode>
311{
312 uint8_t m_task_id;
313
314public:
315 PredicateLoopTask(uint8_t task_id, int32_t) : m_task_id(task_id)
316 {}
317
318private:
319 void Run()
320 {
321 if (m_task_id == 0)
322 {
323 // Producer: let all consumers reach their predicate-Wait() loops, then
324 // notify one at a time until all consumers have exited
326
327 int32_t consumers = _STK_CV_TEST_TASKS_MAX - 1;
328
329 for (int32_t i = 0; i < consumers; ++i)
330 {
331 g_TestMutex.Lock();
332 ++g_SharedCounter; // raise the predicate: one more consumer may proceed
333 g_TestCond.NotifyOne();
334 g_TestMutex.Unlock();
335
336 stk::Delay(1); // pace so each consumer gets a chance to re-evaluate
337 }
338
340
341 printf("predicate loop: counter=%d (expected %d)\n",
342 (int)g_SharedCounter, (int)(_STK_CV_TEST_TASKS_MAX - 1));
343
345 g_TestResult = 1;
346 }
347 else
348 {
349 // Consumers: wait until the predicate is satisfied for them specifically.
350 // Each consumer claims one unit of g_SharedCounter; the loop guards against
351 // spurious wakeups.
352 g_TestMutex.Lock();
353
354 while (g_SharedCounter < (int32_t)m_task_id)
356
357 // predicate satisfied: consumer has logically consumed its slot
358 g_TestMutex.Unlock();
359 }
360 }
361};
362
370template <EAccessMode _AccessMode>
371class NotifyOneOrderTask : public Task<_STK_CV_STACK_SIZE, _AccessMode>
372{
373 uint8_t m_task_id;
374
375public:
376 NotifyOneOrderTask(uint8_t task_id, int32_t) : m_task_id(task_id)
377 {}
378
379private:
380 void Run()
381 {
382 if (m_task_id == 0)
383 {
384 // Producer: let all consumers block first, then wake them one by one
386
387 int32_t consumers = _STK_CV_TEST_TASKS_MAX - 1;
388
389 for (int32_t i = 0; i < consumers; ++i)
390 {
391 g_TestMutex.Lock();
392 g_TestCond.NotifyOne();
393 g_TestMutex.Unlock();
394
395 stk::Delay(1); // give the woken consumer time to record its order entry
396 }
397
399
400 // Verify FIFO: each woken task_id must be strictly greater than the previous
401 bool ordered = true;
402 for (int32_t i = 1; i < consumers; ++i)
403 {
404 if (g_AcquisitionOrder[i] <= g_AcquisitionOrder[i - 1])
405 {
406 ordered = false;
407 break;
408 }
409 }
410
411 printf("notify-one order: order=[%d,%d,%d,%d], ordered=%d (expected 1)\n",
412 (int)g_AcquisitionOrder[0], (int)g_AcquisitionOrder[1],
413 (int)g_AcquisitionOrder[2], (int)g_AcquisitionOrder[3],
414 (int)ordered);
415
416 if (ordered && g_SharedCounter == consumers)
417 g_TestResult = 1;
418 }
419 else
420 {
421 // Consumers: block in order 1, 2, 3, 4; on wake, record arrival position
422 g_TestMutex.Lock();
423
425 {
426 int32_t slot = g_OrderIndex++;
429 }
430
431 g_TestMutex.Unlock();
432 }
433 }
434};
435
443template <EAccessMode _AccessMode>
444class NoWaitTimeoutTask : public Task<_STK_CV_STACK_SIZE, _AccessMode>
445{
446 uint8_t m_task_id;
447
448public:
449 NoWaitTimeoutTask(uint8_t task_id, int32_t) : m_task_id(task_id)
450 {}
451
452private:
453 void Run()
454 {
455 if (m_task_id == 1)
456 {
457 // NO_WAIT must return false immediately with no blocking
458 g_TestMutex.Lock();
459 int64_t start = GetTimeNowMs();
460 bool woken = g_TestCond.Wait(g_TestMutex, NO_WAIT);
461 int64_t elapsed = GetTimeNowMs() - start;
462 g_TestMutex.Unlock();
463
464 if (!woken && elapsed < _STK_CV_TEST_SHORT_SLEEP)
465 ++g_SharedCounter; // 1: returned false immediately
466 }
467 else
468 if (m_task_id == 2)
469 {
470 // Normal WAIT_INFINITE Wait() after NO_WAIT must still work correctly
472
473 g_TestMutex.Lock();
474 bool woken = g_TestCond.Wait(g_TestMutex, _STK_CV_TEST_TIMEOUT);
475 g_TestMutex.Unlock();
476
477 if (woken)
478 ++g_SharedCounter; // 2: woken correctly by producer
479 }
480 else
481 if (m_task_id == 0)
482 {
483 // Producer: fires after task 2 has entered Wait()
485
486 g_TestMutex.Lock();
487 g_TestCond.NotifyOne();
488 g_TestMutex.Unlock();
489
491
492 printf("no-wait timeout: counter=%d (expected 2)\n", (int)g_SharedCounter);
493
494 if (g_SharedCounter == 2)
495 g_TestResult = 1;
496 }
497 }
498};
499
508template <EAccessMode _AccessMode>
509class StressTestTask : public Task<_STK_CV_STACK_SIZE, _AccessMode>
510{
511 uint8_t m_task_id;
513
514public:
515 StressTestTask(uint8_t task_id, int32_t iterations) : m_task_id(task_id), m_iterations(iterations)
516 {}
517
518private:
519 void Run()
520 {
521 for (int32_t i = 0; i < m_iterations; ++i)
522 {
523 if ((i % 2) == 0)
524 {
525 // Producer role: signal one waiter
526 g_TestMutex.Lock();
528 g_TestCond.NotifyOne();
529 g_TestMutex.Unlock();
530 }
531 else
532 {
533 // Consumer role: wait for a signal (may fail under contention if no producer fires)
534 g_TestMutex.Lock();
536 g_TestMutex.Unlock();
537 }
538 }
539
541
543 {
546
547 // Minimum guaranteed: producer iterations only (m_iterations / 2 per task,
548 // rounded up since even index 0 is always a producer iteration)
549 int32_t min_expected = _STK_CV_TEST_TASKS_MAX * ((m_iterations + 1) / 2);
550
551 printf("stress test: counter=%d (min expected %d)\n",
552 (int)g_SharedCounter, (int)min_expected);
553
554 if (g_SharedCounter >= min_expected)
555 g_TestResult = 1;
556 }
557 }
558};
559
560// Helper function to reset test state
561static void ResetTestState()
562{
563 g_TestResult = 0;
564 g_SharedCounter = 0;
565 g_OrderIndex = 0;
566 g_TestComplete = false;
567 g_InstancesDone = 0;
568
569 for (int32_t i = 0; i < _STK_CV_TEST_TASKS_MAX; ++i)
570 g_AcquisitionOrder[i] = 0;
571
572 // Re-construct the mutex and condition variable in-place for a clean state
573 g_TestMutex.~Mutex();
574 new (&g_TestMutex) sync::Mutex();
575
576 g_TestCond.~ConditionVariable();
578}
579
580} // namespace condvar
581} // namespace test
582} // namespace stk
583
584static bool NeedsExtendedTasks(const char *test_name)
585{
586 return (strcmp(test_name, "TimeoutExpires") != 0) &&
587 (strcmp(test_name, "MutexReacquired") != 0) &&
588 (strcmp(test_name, "NoWaitTimeout") != 0);
589}
590
594template <class TaskType>
595static int32_t RunTest(const char *test_name, int32_t param = 0)
596{
597 using namespace stk;
598 using namespace stk::test;
599 using namespace stk::test::condvar;
600
601 printf("Test: %s\n", test_name);
602
604
605 // Create tasks based on test type
606 STK_TASK TaskType task0(0, param);
607 STK_TASK TaskType task1(1, param);
608 STK_TASK TaskType task2(2, param);
609 TaskType task3(3, param);
610 TaskType task4(4, param);
611
612 g_Kernel.AddTask(&task0);
613 g_Kernel.AddTask(&task1);
614 g_Kernel.AddTask(&task2);
615
616 if (NeedsExtendedTasks(test_name))
617 {
618 g_Kernel.AddTask(&task3);
619 g_Kernel.AddTask(&task4);
620 }
621
622 g_Kernel.Start();
623
625
626 printf("Result: %s\n", result == TestContext::SUCCESS_EXIT_CODE ? "PASS" : "FAIL");
627 printf("--------------\n");
628
629 return result;
630}
631
635int main(int argc, char **argv)
636{
637 (void)argc;
638 (void)argv;
639
640 using namespace stk::test::condvar;
641
643
644 int total_failures = 0, total_success = 0;
645
646 printf("--------------\n");
647
648 g_Kernel.Initialize();
649
650#ifndef __ARM_ARCH_6M__
651
652 // Test 1: NotifyOne() wakes exactly one waiting task per call
654 total_failures++;
655 else
656 total_success++;
657
658 // Test 2: NotifyAll() wakes every waiting task in a single call
660 total_failures++;
661 else
662 total_success++;
663
664 // Test 3: Wait() returns false within correct time window when no notification arrives (tasks 0-2 only)
666 total_failures++;
667 else
668 total_success++;
669
670 // Test 4: Wait() atomically releases mutex and re-acquires it before returning (tasks 0-2 only)
672 total_failures++;
673 else
674 total_success++;
675
676 // Test 5: Spurious-wakeup-safe predicate loop correctly drives all consumers to completion
678 total_failures++;
679 else
680 total_success++;
681
682 // Test 6: NotifyOne() releases waiters in FIFO arrival order
684 total_failures++;
685 else
686 total_success++;
687
688 // Test 7: Wait(NO_WAIT) returns false immediately without blocking (tasks 0-2 only)
690 total_failures++;
691 else
692 total_success++;
693
694#endif // __ARM_ARCH_6M__
695
696 // Test 8: Stress test under full five-task contention with alternating producer/consumer roles
698 total_failures++;
699 else
700 total_success++;
701
702 int32_t final_result = (total_failures == 0 ? TestContext::SUCCESS_EXIT_CODE : TestContext::DEFAULT_FAILURE_EXIT_CODE);
703
704 printf("##############\n");
705 printf("Total tests: %d\n", total_failures + total_success);
706 printf("Failures: %d\n", total_failures);
707
709 return final_result;
710}
Top-level STK include. Provides the Kernel class template and all built-in task-switching strategies.
Implementation of synchronization primitive: stk::sync::ConditionVariable.
Implementation of synchronization primitive: stk::sync::Mutex.
#define _STK_CV_TEST_SHORT_SLEEP
#define _STK_CV_TEST_LONG_SLEEP
int main(int argc, char **argv)
static int32_t RunTest(const char *test_name, int32_t param=0)
#define _STK_CV_TEST_TIMEOUT
#define STK_TASK
static bool NeedsExtendedTasks(const char *test_name)
#define _STK_CV_TEST_TASKS_MAX
#define STK_TEST_DECL_ASSERT
Declare assertion redirector in the source file.
Namespace of STK package.
static int64_t GetTimeNowMs()
Get current time in milliseconds since kernel start.
Definition stk_helper.h:281
void Sleep(uint32_t ticks)
Put calling process into a sleep state.
Definition stk_helper.h:298
void Delay(uint32_t ticks)
Delay calling process by busy-waiting until the deadline expires.
Definition stk_helper.h:342
const Timeout NO_WAIT
Timeout value: return immediately if the synchronization object is not yet signaled (non-blocking pol...
Definition stk_common.h:145
Namespace of the test inventory.
Namespace of ConditionVariable test.
static Kernel< KERNEL_DYNAMIC|KERNEL_SYNC, 5, SwitchStrategyRR, PlatformDefault > g_Kernel
static volatile int32_t g_InstancesDone
static volatile bool g_TestComplete
static sync::Mutex g_TestMutex
static volatile int32_t g_OrderIndex
static sync::ConditionVariable g_TestCond
static volatile int32_t g_SharedCounter
static volatile int32_t g_TestResult
static volatile int32_t g_AcquisitionOrder[5]
static void ResetTestState()
Concrete implementation of IKernel.
Definition stk.h:83
Task(const Task &)=delete
Condition Variable primitive for signaling between tasks based on specific predicates.
Definition stk_sync_cv.h:68
Recursive mutex primitive that allows the same thread to acquire the lock multiple times.
Tests that NotifyOne() wakes exactly one waiting task per call.
void Run()
Entry point of the user task.
NotifyOneWakesTask(uint8_t task_id, int32_t iterations)
Tests that NotifyAll() wakes every waiting task in one call.
void Run()
Entry point of the user task.
NotifyAllWakesTask(uint8_t task_id, int32_t)
Tests that Wait() returns false within the expected time when no notification arrives.
TimeoutExpiresTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
Tests that Wait() atomically releases the mutex and re-acquires it before returning.
MutexReacquiredTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
Tests the canonical spurious-wakeup-safe predicate loop pattern.
void Run()
Entry point of the user task.
PredicateLoopTask(uint8_t task_id, int32_t)
Tests that NotifyOne() releases waiters in FIFO arrival order.
NotifyOneOrderTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
Tests that Wait(NO_WAIT) returns false immediately without blocking.
NoWaitTimeoutTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
Stress test of ConditionVariable under full five-task contention.
void Run()
Entry point of the user task.
StressTestTask(uint8_t task_id, int32_t iterations)
static void ShowTestSuitePrologue()
Show text string as prologue before tests start.
@ DEFAULT_FAILURE_EXIT_CODE
default exit code for exit() to denote failure of the test
@ SUCCESS_EXIT_CODE
exit code for exit() to denote the success of the test
static void ShowTestSuiteEpilogue(int32_t result)
Show text string as epilogue after tests end.