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_mutex.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 <assert.h>
14#include <string.h>
15
16#include "stktest_context.h"
17
18using namespace stk;
19using namespace stk::test;
20
22
23#define _STK_MUTEX_TEST_TASKS_MAX 5
24#define _STK_MUTEX_TEST_TIMEOUT 1000
25#define _STK_MUTEX_TEST_SHORT_SLEEP 10
26#define _STK_MUTEX_TEST_LONG_SLEEP 100
27#ifdef __ARM_ARCH_6M__
28#define _STK_MUTEX_STACK_SIZE 128 // ARM Cortex-M0
29#define STK_TASK
30#else
31#define _STK_MUTEX_STACK_SIZE 256
32#define STK_TASK static
33#endif
34
35namespace stk {
36namespace test {
37
41namespace mutex {
42
43// Test results storage
44static volatile int32_t g_TestResult = 0;
45static volatile int32_t g_SharedCounter = 0;
46static volatile int32_t g_AcquisitionOrder[_STK_MUTEX_TEST_TASKS_MAX] = {0};
47static volatile int32_t g_OrderIndex = 0;
48static volatile bool g_TestComplete = false;
49static volatile int32_t g_InstancesDone = 0;
50
51// Kernel
54
55// Test mutex
57
62template <EAccessMode _AccessMode>
63class BasicLockUnlockTask : public Task<_STK_MUTEX_STACK_SIZE, _AccessMode>
64{
65 uint8_t m_task_id;
66 int32_t m_iterations;
67
68public:
69 BasicLockUnlockTask(uint8_t task_id, int32_t iterations) : m_task_id(task_id), m_iterations(iterations)
70 {}
71
72private:
73 void Run()
74 {
75 int32_t workload = 0;
76
77 for (int32_t i = 0; i < m_iterations; ++i)
78 {
79 g_TestMutex.Lock();
80
81 // Critical section - increment shared counter
82 int32_t temp = g_SharedCounter;
83 if (++workload % 4 == 0)
84 stk::Delay(1); // Small delay to increase chance of race if mutex broken
85 g_SharedCounter = temp + 1;
86
87 g_TestMutex.Unlock();
88
89 stk::Yield(); // Yield to other tasks
90 }
91
93
94 // Task 0 acts as verifier: waits for all other tasks to finish then checks
95 // that the counter equals exactly tasks_count * iterations, confirming that
96 // no increment was lost or doubled due to a broken mutual exclusion.
97 if (m_task_id == 0)
98 {
101
102 int32_t expected = _STK_MUTEX_TEST_TASKS_MAX * m_iterations;
103
104 printf("basic lock/unlock: counter=%d (expected %d)\n", (int)g_SharedCounter, (int)expected);
105
106 if (g_SharedCounter == expected)
107 g_TestResult = 1;
108 }
109 }
110};
111
116template <EAccessMode _AccessMode>
117class RecursiveLockTask : public Task<_STK_MUTEX_STACK_SIZE, _AccessMode>
118{
119 uint8_t m_task_id;
120
121public:
122 RecursiveLockTask(uint8_t task_id, int32_t) : m_task_id(task_id)
123 {}
124
125private:
126 void Run()
127 {
128 g_TestMutex.Lock();
129 {
130 g_TestMutex.Lock(); // Recursive acquisition
131 {
132 g_TestMutex.Lock(); // Third level
133 {
135 }
136 g_TestMutex.Unlock();
137 }
138 g_TestMutex.Unlock();
139 }
140 g_TestMutex.Unlock();
141
143
144 // Verify counter was incremented exactly once per task
145 if (m_task_id == 0)
146 {
149
150 int32_t expected = _STK_MUTEX_TEST_TASKS_MAX;
151
152 printf("recursive lock/unlock: counter=%d (expected %d)\n", (int)g_SharedCounter, (int)expected);
153
155 g_TestResult = 1;
156 }
157 }
158};
159
164template <EAccessMode _AccessMode>
165class TryLockTask : public Task<_STK_MUTEX_STACK_SIZE, _AccessMode>
166{
167 uint8_t m_task_id;
168
169public:
170 TryLockTask(uint8_t task_id, int32_t) : m_task_id(task_id)
171 {}
172
173private:
174 void Run()
175 {
176 if (m_task_id == 0)
177 {
178 // Task 0: Hold the lock
179 g_TestMutex.Lock();
180 g_SharedCounter = 1;
182 g_TestMutex.Unlock();
183 }
184 else
185 if (m_task_id == 1)
186 {
187 // Task 1: Try to acquire while held — must fail immediately
188 stk::Sleep(_STK_MUTEX_TEST_SHORT_SLEEP); // Let task 0 acquire first
189
190 int64_t start = GetTimeNowMs();
191 bool acquired = g_TestMutex.TryLock();
192 int64_t elapsed = GetTimeNowMs() - start;
193
194 if (!acquired && (elapsed < _STK_MUTEX_TEST_SHORT_SLEEP))
195 g_TestResult = 1;
196 else
197 g_TestResult = 0;
198
199 if (acquired)
200 g_TestMutex.Unlock();
201 }
202 // Tasks 2+ are not used by this test
203
205 }
206};
207
212template <EAccessMode _AccessMode>
213class TimedLockTask : public Task<_STK_MUTEX_STACK_SIZE, _AccessMode>
214{
215 uint8_t m_task_id;
216
217public:
218 TimedLockTask(uint8_t task_id, int32_t) : m_task_id(task_id)
219 {}
220
221private:
222 void Run()
223 {
224 if (m_task_id == 0)
225 {
226 // Task 0: Hold the lock for extended period
227 g_TestMutex.Lock();
228 stk::Sleep(200); // Hold for 200ms
229 g_TestMutex.Unlock();
230 }
231 else
232 if (m_task_id == 1)
233 {
234 // Task 1: Try to acquire with timeout
235 stk::Sleep(_STK_MUTEX_TEST_SHORT_SLEEP); // Let task 0 acquire first
236
237 int64_t start = GetTimeNowMs();
238 bool acquired = g_TestMutex.TimedLock(50); // 50ms timeout
239 int64_t elapsed = GetTimeNowMs() - start;
240
241 // Should timeout after ~50ms
242 if (!acquired && elapsed >= 45 && elapsed <= 60)
243 {
245 }
246
247 if (acquired)
248 g_TestMutex.Unlock();
249 }
250 else
251 if (m_task_id == 2)
252 {
253 // Task 2: Successfully acquire after task 0 releases
254 stk::Sleep(250); // Wait for task 0 to release
255
256 if (g_TestMutex.TimedLock(100))
257 {
259 g_TestMutex.Unlock();
260 }
261 }
262
264
265 // Final check
266 if (m_task_id == 2)
267 {
269
270 if (g_SharedCounter == 2)
271 g_TestResult = 1;
272 }
273 }
274};
275
280template <EAccessMode _AccessMode>
281class FIFOOrderTask : public Task<_STK_MUTEX_STACK_SIZE, _AccessMode>
282{
283 uint8_t m_task_id;
284
285public:
286 FIFOOrderTask(uint8_t task_id, int32_t) : m_task_id(task_id)
287 {}
288
289private:
290 void Run()
291 {
292 if (m_task_id == 0)
293 {
294 // Task 0: Acquire lock first
295 g_TestMutex.Lock();
296 stk::Sleep(50); // Hold to let others queue up
297 g_TestMutex.Unlock();
298 }
299 else
300 {
301 // Tasks 1-4: Wait in order
302 stk::Sleep(_STK_MUTEX_TEST_SHORT_SLEEP * m_task_id); // Stagger start times
303
304 g_TestMutex.Lock();
305 {
306 // Record acquisition order
307 int32_t idx = g_OrderIndex++;
309 }
310 g_TestMutex.Unlock();
311 }
312
314
315 // Task 4 verifies order
317 {
320
321 // Check if tasks acquired in FIFO order (1, 2, 3, 4)
322 bool ordered = true;
323 for (int32_t i = 0; i < (_STK_MUTEX_TEST_TASKS_MAX - 1); ++i)
324 {
325 if (g_AcquisitionOrder[i] != (i + 1))
326 {
327 ordered = false;
328 printf("Order violation: position %d has task %d (expected %d)\n",
329 (int)i, (int)g_AcquisitionOrder[i], (int)(i + 1));
330 break;
331 }
332 }
333
334 if (ordered)
335 g_TestResult = 1;
336 }
337 }
338};
339
344template <EAccessMode _AccessMode>
345class StressTestTask : public Task<_STK_MUTEX_STACK_SIZE, _AccessMode>
346{
347 uint8_t m_task_id;
349
350public:
351 StressTestTask(uint8_t task_id, int32_t iterations) : m_task_id(task_id), m_iterations(iterations)
352 {}
353
354private:
355 void Run()
356 {
357 for (int32_t i = 0; i < m_iterations; ++i)
358 {
359 // Mix of operations
360 if (i % 3 == 0)
361 {
362 // Regular lock
363 g_TestMutex.Lock();
365 g_TestMutex.Unlock();
366 }
367 else
368 if (i % 3 == 1)
369 {
370 // Try lock
371 if (g_TestMutex.TryLock())
372 {
374 g_TestMutex.Unlock();
375 }
376 }
377 else
378 {
379 // Timed lock
380 if (g_TestMutex.TimedLock(10))
381 {
383 g_TestMutex.Unlock();
384 }
385 }
386
387 if ((i % 10) == 0)
388 stk::Delay(1);
389 }
390
392
393 // Last task verifies total
395 {
398
399 // All increments should be accounted for (may be less if TryLock failed)
400 if (g_SharedCounter > 0)
401 g_TestResult = 1;
402
403 printf("Stress test: counter=%d\n", (int)g_SharedCounter);
404 }
405 }
406};
407
412template <EAccessMode _AccessMode>
413class RecursiveDepthTask : public Task<_STK_MUTEX_STACK_SIZE, _AccessMode>
414{
415 uint8_t m_task_id;
416 enum { DEPTH = 3 };
417
418public:
419 RecursiveDepthTask(uint8_t task_id, int32_t) : m_task_id(task_id)
420 {}
421
422private:
423 void RecursiveLock(int32_t depth)
424 {
425 if (depth == 0)
426 return;
427
428 g_TestMutex.Lock();
429 RecursiveLock(depth - 1);
431 g_TestMutex.Unlock();
432 }
433
434 void Run()
435 {
436 // Recursive lock to depth DEPTH
438
440
441 if (m_task_id == 0)
442 {
444
445 int32_t expected = _STK_MUTEX_TEST_TASKS_MAX * DEPTH;
446
447 printf("recursive depth: counter=%d (expected %d)\n", (int)g_SharedCounter, (int)expected);
448
449 if (g_SharedCounter == expected)
450 g_TestResult = 1;
451 }
452 }
453};
454
459template <EAccessMode _AccessMode>
460class InterTaskCoordinationTask : public Task<_STK_MUTEX_STACK_SIZE, _AccessMode>
461{
462 uint8_t m_task_id;
463
464public:
465 InterTaskCoordinationTask(uint8_t task_id, int32_t) : m_task_id(task_id)
466 {}
467
468private:
469 void Run()
470 {
471 for (int32_t round = 0; round < 10; ++round)
472 {
473 g_TestMutex.Lock();
474 {
475 // Each task increments in sequence
477 {
478 g_TestMutex.Unlock();
479 stk::Delay(1);
480 g_TestMutex.Lock();
481 }
482
484 }
485 g_TestMutex.Unlock();
486 }
487
489
490 // Last task verifies
492 {
495
496 // Should be exactly 10 rounds * number of tasks
498 g_TestResult = 1;
499
500 printf("Coordination test: counter=%d (expected %d)\n",
502 }
503 }
504};
505
506// Helper function to reset test state
507static void ResetTestState()
508{
509 g_TestResult = 0;
510 g_SharedCounter = 0;
511 g_OrderIndex = 0;
512 g_TestComplete = false;
513 g_InstancesDone = 0;
514
515 for (int32_t i = 0; i < _STK_MUTEX_TEST_TASKS_MAX; ++i)
516 g_AcquisitionOrder[i] = 0;
517}
518
519} // namespace mutex
520} // namespace test
521} // namespace stk
522
527static bool NeedsExtendedTasks(const char *test_name)
528{
529 return (strcmp(test_name, "TryLock") != 0) &&
530 (strcmp(test_name, "TimedLock") != 0);
531}
532
537static bool NeedsThreeTasks(const char *test_name)
538{
539 return (strcmp(test_name, "TryLock") != 0);
540}
541
545template <class TaskType>
546static int32_t RunTest(const char *test_name, int32_t param = 0)
547{
548 using namespace stk;
549 using namespace stk::test;
550 using namespace stk::test::mutex;
551
552 printf("Test: %s\n", test_name);
553
555
556 // Create tasks based on test type
557 STK_TASK TaskType task0(0, param);
558 STK_TASK TaskType task1(1, param);
559 TaskType task2(2, param);
560 TaskType task3(3, param);
561 TaskType task4(4, param);
562
563 g_Kernel.AddTask(&task0);
564 g_Kernel.AddTask(&task1);
565
566 if (NeedsThreeTasks(test_name))
567 g_Kernel.AddTask(&task2);
568
569 if (NeedsExtendedTasks(test_name))
570 {
571 g_Kernel.AddTask(&task3);
572 g_Kernel.AddTask(&task4);
573 }
574
575 g_Kernel.Start();
576
578
579 printf("Result: %s\n", result == TestContext::SUCCESS_EXIT_CODE ? "PASS" : "FAIL");
580 printf("--------------\n");
581
582 return result;
583}
584
588int main(int argc, char **argv)
589{
590 (void)argc;
591 (void)argv;
592
593 using namespace stk::test::mutex;
594
596
597 int total_failures = 0, total_success = 0;
598
599 printf("--------------\n");
600
601 g_Kernel.Initialize();
602
603#ifndef __ARM_ARCH_6M__
604
605 // Test 1: Basic Lock/Unlock with mutual exclusion
607 total_failures++;
608 else
609 total_success++;
610
611 // Test 2: Recursive locking
613 total_failures++;
614 else
615 total_success++;
616
617 // Test 3: TryLock non-blocking behavior
619 total_failures++;
620 else
621 total_success++;
622
623 // Test 4: TimedLock timeout behavior
625 total_failures++;
626 else
627 total_success++;
628
629 // Test 5: FIFO ordering
631 total_failures++;
632 else
633 total_success++;
634
635 // Test 6: Deep recursion
637 total_failures++;
638 else
639 total_success++;
640
641 // Test 7: Inter-task coordination
643 total_failures++;
644 else
645 total_success++;
646
647#endif // __ARM_ARCH_6M__
648
649 // Test 8: Stress test
651 total_failures++;
652 else
653 total_success++;
654
655 int32_t final_result = (total_failures == 0 ? TestContext::SUCCESS_EXIT_CODE : TestContext::DEFAULT_FAILURE_EXIT_CODE);
656
657 printf("##############\n");
658 printf("Total tests: %d\n", total_failures + total_success);
659 printf("Failures: %d\n", (int)total_failures);
660
662 return final_result;
663}
Top-level STK include. Provides the Kernel class template and all built-in task-switching strategies.
#define STK_TICKLESS_IDLE
Enables tickless (dynamic-tick) low-power operation during idle periods.
Definition stk_defs.h:36
Implementation of synchronization primitive: stk::sync::Mutex.
static int32_t RunTest(const char *test_name, int32_t param=0)
#define STK_TASK
#define _STK_MUTEX_TEST_LONG_SLEEP
#define _STK_MUTEX_TEST_TASKS_MAX
int main(int argc, char **argv)
static bool NeedsThreeTasks(const char *test_name)
static int32_t RunTest(const char *test_name, int32_t param=0)
#define _STK_MUTEX_TEST_SHORT_SLEEP
static bool NeedsExtendedTasks(const char *test_name)
#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 Yield()
Notify scheduler to switch to the next runnable task.
Definition stk_helper.h:331
void Delay(uint32_t ticks)
Delay calling process by busy-waiting until the deadline expires.
Definition stk_helper.h:342
PlatformArmCortexM PlatformDefault
Default platform implementation.
SwitchStrategyRoundRobin SwitchStrategyRR
Shorthand alias for SwitchStrategyRoundRobin.
@ KERNEL_TICKLESS
Tickless mode. To use this mode STK_TICKLESS_IDLE must be defined to 1 in stk_config....
Definition stk_common.h:45
@ KERNEL_SYNC
Synchronization support (see Event).
Definition stk_common.h:44
@ KERNEL_DYNAMIC
Tasks can be added or removed and therefore exit when done.
Definition stk_common.h:42
Namespace of the test inventory.
Namespace of Mutex test.
static volatile int32_t g_InstancesDone
static volatile int32_t g_OrderIndex
static volatile int32_t g_TestResult
static volatile int32_t g_SharedCounter
static Kernel< KERNEL_DYNAMIC|KERNEL_SYNC|(STK_TICKLESS_IDLE ? KERNEL_TICKLESS :0), 5, SwitchStrategyRR, PlatformDefault > g_Kernel
static volatile bool g_TestComplete
static volatile int32_t g_AcquisitionOrder[5]
static void ResetTestState()
static sync::Mutex g_TestMutex
Concrete implementation of IKernel.
Definition stk.h:83
Task(const Task &)=delete
Recursive mutex primitive that allows the same thread to acquire the lock multiple times.
Tests basic lock/unlock functionality.
void Run()
Entry point of the user task.
BasicLockUnlockTask(uint8_t task_id, int32_t iterations)
Tests recursive locking capability.
RecursiveLockTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
Tests TryLock() non-blocking behavior.
void Run()
Entry point of the user task.
TryLockTask(uint8_t task_id, int32_t)
Tests TimedLock() timeout behavior.
void Run()
Entry point of the user task.
TimedLockTask(uint8_t task_id, int32_t)
Tests FIFO ordering of waiting threads.
FIFOOrderTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
Stress test with many lock/unlock cycles.
void Run()
Entry point of the user task.
StressTestTask(uint8_t task_id, int32_t iterations)
Tests deep recursive locking.
void Run()
Entry point of the user task.
RecursiveDepthTask(uint8_t task_id, int32_t)
Tests mutex for coordinating work between tasks.
InterTaskCoordinationTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
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.