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_spinlock.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>
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_SL_TEST_TASKS_MAX 5
24#define _STK_SL_TEST_SHORT_SLEEP 10
25#define _STK_SL_TEST_LONG_SLEEP 100
26#ifdef __ARM_ARCH_6M__
27#define _STK_SL_STACK_SIZE 128 // ARM Cortex-M0
28#define STK_TASK
29#else
30#define _STK_SL_STACK_SIZE 256
31#define STK_TASK static
32#endif
33
34namespace stk {
35namespace test {
36
40namespace spinlock {
41
42// Test results storage
43static volatile int32_t g_TestResult = 0;
44static volatile int32_t g_SharedCounter = 0;
45static volatile int32_t g_OrderIndex = 0;
46static volatile bool g_TestComplete = false;
47static volatile int32_t g_InstancesDone = 0;
48
49// Kernel (SpinLock does not require KERNEL_SYNC)
51
52// Test spinlock - shared across tasks; low spin count forces Yield() quickly under contention
54
61template <EAccessMode _AccessMode>
62class MutualExclusionTask : public Task<_STK_SL_STACK_SIZE, _AccessMode>
63{
64 uint8_t m_task_id;
65 int32_t m_iterations;
66
67public:
68 MutualExclusionTask(uint8_t task_id, int32_t iterations) : m_task_id(task_id), m_iterations(iterations)
69 {}
70
71private:
72 void Run()
73 {
74 int32_t workload = 0;
75
76 for (int32_t i = 0; i < m_iterations; ++i)
77 {
78 g_TestSpinLock.Lock();
79
80 // read-modify-Yield-write: exposes races if the lock is not working
81 int32_t temp = g_SharedCounter;
82 if (++workload % 4 == 0)
83 stk::Delay(1); // Small delay to increase chance of race if mutex broken
84 g_SharedCounter = temp + 1;
85
86 g_TestSpinLock.Unlock();
87
88 stk::Yield(); // Yield to other tasks
89 }
90
92
93 if (m_task_id == 0)
94 {
97
98 int32_t expected = _STK_SL_TEST_TASKS_MAX * m_iterations;
99
100 printf("mutual exclusion: counter=%d (expected %d)\n",
101 (int)g_SharedCounter, (int)expected);
102
103 if (g_SharedCounter == expected)
104 g_TestResult = 1;
105 }
106 }
107};
108
115template <EAccessMode _AccessMode>
116class TryLockFreeTask : public Task<_STK_SL_STACK_SIZE, _AccessMode>
117{
118 uint8_t m_task_id;
119
120public:
121 TryLockFreeTask(uint8_t task_id, int32_t) : m_task_id(task_id)
122 {}
123
124private:
125 void Run()
126 {
127 if (m_task_id == 1)
128 {
129 // TryLock on a free lock must succeed immediately
130 bool acquired = g_TestSpinLock.TryLock();
131
132 if (acquired)
133 {
134 // Signal task 2 to attempt TryLock while we still hold it (counter=2)
135 g_SharedCounter = 2;
136
137 stk::Sleep(_STK_SL_TEST_SHORT_SLEEP); // hold while task 2 tries
138
139 g_TestSpinLock.Unlock();
140
141 // Re-acquire after release to confirm the lock is free again
142 if (g_TestSpinLock.TryLock())
143 {
144 ++g_SharedCounter; // 4: re-acquired after release
145 g_TestSpinLock.Unlock();
146 }
147 }
148 }
149 else
150 if (m_task_id == 2)
151 {
152 // Wait until task 1 signals it holds the lock
153 while (g_SharedCounter < 2)
154 stk::Yield();
155
156 // Must fail: task 1 still holds the lock
157 bool acquired = g_TestSpinLock.TryLock();
158
159 if (!acquired)
160 ++g_SharedCounter; // 3: correctly returned false
161
162 if (acquired)
163 g_TestSpinLock.Unlock();
164 }
165
167
168 if (m_task_id == 0)
169 {
171
172 printf("try-lock free: counter=%d (expected 4)\n", (int)g_SharedCounter);
173
174 if (g_SharedCounter == 4)
175 g_TestResult = 1;
176 }
177 }
178};
179
185template <EAccessMode _AccessMode>
186class TryLockContendedTask : public Task<_STK_SL_STACK_SIZE, _AccessMode>
187{
188 uint8_t m_task_id;
189
190public:
191 TryLockContendedTask(uint8_t task_id, int32_t) : m_task_id(task_id)
192 {}
193
194private:
195 void Run()
196 {
197 if (m_task_id == 0)
198 {
199 // Acquire and hold; signal task 1 that the lock is held
200 g_TestSpinLock.Lock();
201 g_SharedCounter = 1;
203 g_TestSpinLock.Unlock();
204 }
205 else
206 if (m_task_id == 1)
207 {
208 // Wait until task 0 signals it has acquired the lock
209 while (g_SharedCounter == 0)
210 stk::Yield();
211
212 int64_t start = GetTimeNowMs();
213 bool acquired = g_TestSpinLock.TryLock();
214 int64_t elapsed = GetTimeNowMs() - start;
215
216 // Must fail immediately: task 0 holds the lock
217 if (!acquired && elapsed < _STK_SL_TEST_SHORT_SLEEP)
218 ++g_SharedCounter; // 2: correctly returned false immediately
219
220 if (acquired)
221 g_TestSpinLock.Unlock();
222 }
223 else
224 if (m_task_id == 2)
225 {
227
228 printf("try-lock contended: counter=%d (expected 2)\n", (int)g_SharedCounter);
229
230 if (g_SharedCounter == 2)
231 g_TestResult = 1;
232 }
233
235 }
236};
237
245template <EAccessMode _AccessMode>
246class RecursiveLockTask : public Task<_STK_SL_STACK_SIZE, _AccessMode>
247{
248 uint8_t m_task_id;
249 enum { DEPTH = 3 };
250
251public:
252 RecursiveLockTask(uint8_t task_id, int32_t) : m_task_id(task_id)
253 {}
254
255private:
256 void AcquireRecursive(int32_t depth)
257 {
258 if (depth == 0)
259 return;
260
261 g_TestSpinLock.Lock();
262 AcquireRecursive(depth - 1);
264 g_TestSpinLock.Unlock();
265 }
266
267 void Run()
268 {
270
272
273 if (m_task_id == 0)
274 {
277
278 int32_t expected = _STK_SL_TEST_TASKS_MAX * DEPTH;
279
280 printf("recursive lock: counter=%d (expected %d)\n",
281 (int)g_SharedCounter, (int)expected);
282
283 if (g_SharedCounter == expected)
284 g_TestResult = 1;
285 }
286 }
287};
288
296template <EAccessMode _AccessMode>
297class RecursiveTryLockTask : public Task<_STK_SL_STACK_SIZE, _AccessMode>
298{
299 uint8_t m_task_id;
300
301public:
302 RecursiveTryLockTask(uint8_t task_id, int32_t) : m_task_id(task_id)
303 {}
304
305private:
306 void Run()
307 {
308 if (m_task_id == 1)
309 {
310 // Acquire recursively via TryLock three levels deep
311 bool l1 = g_TestSpinLock.TryLock(); // depth 1
312 bool l2 = g_TestSpinLock.TryLock(); // depth 2 - recursive, must succeed
313 bool l3 = g_TestSpinLock.TryLock(); // depth 3 - recursive, must succeed
314
315 if (l1 && l2 && l3)
316 ++g_SharedCounter; // 1: all three TryLock() calls succeeded
317
318 g_TestSpinLock.Unlock(); // depth 2 remains
319 g_TestSpinLock.Unlock(); // depth 1 remains
320
321 // Signal task 2: task 1 still holds depth 1, task 2 must fail TryLock
322 g_SharedCounter = 2;
323
324 stk::Sleep(_STK_SL_TEST_SHORT_SLEEP); // hold for task 2 to attempt
325
326 g_TestSpinLock.Unlock(); // depth 0 - fully released
327 }
328 else
329 if (m_task_id == 2)
330 {
331 // Wait for task 1 to signal it still holds the lock at depth 1
332 while (g_SharedCounter < 2)
333 stk::Yield();
334
335 bool acquired = g_TestSpinLock.TryLock();
336
337 if (!acquired)
338 ++g_SharedCounter; // 3: correctly blocked while task 1 held depth 1
339
340 if (acquired)
341 g_TestSpinLock.Unlock();
342 }
343
345
346 if (m_task_id == 0)
347 {
349
350 printf("recursive try-lock: counter=%d (expected 3)\n", (int)g_SharedCounter);
351
352 if (g_SharedCounter == 3)
353 g_TestResult = 1;
354 }
355 }
356};
357
365template <EAccessMode _AccessMode>
366class YieldUnderContentionTask : public Task<_STK_SL_STACK_SIZE, _AccessMode>
367{
368 uint8_t m_task_id;
370
371public:
372 YieldUnderContentionTask(uint8_t task_id, int32_t iterations) : m_task_id(task_id), m_iterations(iterations)
373 {}
374
375private:
376 void Run()
377 {
378 // All tasks hammer the lock simultaneously; low spin count forces frequent Yield()
379 for (int32_t i = 0; i < m_iterations; ++i)
380 {
381 g_TestSpinLock.Lock();
383 g_TestSpinLock.Unlock();
384 }
385
387
388 if (m_task_id == 0)
389 {
392
393 int32_t expected = _STK_SL_TEST_TASKS_MAX * m_iterations;
394
395 printf("yield under contention: counter=%d (expected %d)\n",
396 (int)g_SharedCounter, (int)expected);
397
398 if (g_SharedCounter == expected)
399 g_TestResult = 1;
400 }
401 }
402};
403
410template <EAccessMode _AccessMode>
411class UnlockTransferTask : public Task<_STK_SL_STACK_SIZE, _AccessMode>
412{
413 uint8_t m_task_id;
415
416public:
417 UnlockTransferTask(uint8_t task_id, int32_t iterations) : m_task_id(task_id), m_iterations(iterations)
418 {}
419
420private:
421 void Run()
422 {
423 for (int32_t i = 0; i < m_iterations; ++i)
424 {
425 g_TestSpinLock.Lock();
427 g_TestSpinLock.Unlock();
428
429 stk::Delay(1); // stagger re-acquisitions to spread lock transfers across all tasks
430 }
431
433
434 if (m_task_id == 0)
435 {
438
439 int32_t expected = _STK_SL_TEST_TASKS_MAX * m_iterations;
440
441 printf("unlock transfer: counter=%d (expected %d)\n",
442 (int)g_SharedCounter, (int)expected);
443
444 if (g_SharedCounter == expected)
445 g_TestResult = 1;
446 }
447 }
448};
449
458template <EAccessMode _AccessMode>
459class StressTestTask : public Task<_STK_SL_STACK_SIZE, _AccessMode>
460{
461 uint8_t m_task_id;
463
464public:
465 StressTestTask(uint8_t task_id, int32_t iterations) : m_task_id(task_id), m_iterations(iterations)
466 {}
467
468private:
469 void Run()
470 {
471 for (int32_t i = 0; i < m_iterations; ++i)
472 {
473 if ((i % 2) == 0)
474 {
475 // Blocking path: always acquires
476 g_TestSpinLock.Lock();
478 g_TestSpinLock.Unlock();
479 }
480 else
481 {
482 // Non-blocking path: may fail under contention
483 if (g_TestSpinLock.TryLock())
484 {
486 g_TestSpinLock.Unlock();
487 }
488 }
489 }
490
492
494 {
497
498 // Minimum guaranteed: blocking iterations only (m_iterations / 2 per task)
499 int32_t min_expected = _STK_SL_TEST_TASKS_MAX * (m_iterations / 2);
500
501 printf("stress test: counter=%d (min expected %d)\n",
502 (int)g_SharedCounter, (int)min_expected);
503
504 if (g_SharedCounter >= min_expected)
505 g_TestResult = 1;
506 }
507 }
508};
509
510// Helper function to reset test state
511static void ResetTestState()
512{
513 g_TestResult = 0;
514 g_SharedCounter = 0;
515 g_OrderIndex = 0;
516 g_TestComplete = false;
517 g_InstancesDone = 0;
518}
519
520} // namespace spinlock
521} // namespace test
522} // namespace stk
523
524static bool NeedsExtendedTasks(const char *test_name)
525{
526 return (strcmp(test_name, "TryLockFree") != 0) &&
527 (strcmp(test_name, "TryLockContended") != 0) &&
528 (strcmp(test_name, "RecursiveTryLock") != 0);
529}
530
534template <class TaskType>
535static int32_t RunTest(const char *test_name, int32_t param = 0)
536{
537 using namespace stk;
538 using namespace stk::test;
539 using namespace stk::test::spinlock;
540
541 printf("Test: %s\n", test_name);
542
544
545 // Create tasks based on test type
546 STK_TASK TaskType task0(0, param);
547 STK_TASK TaskType task1(1, param);
548 STK_TASK TaskType task2(2, param);
549 TaskType task3(3, param);
550 TaskType task4(4, param);
551
552 g_Kernel.AddTask(&task0);
553 g_Kernel.AddTask(&task1);
554 g_Kernel.AddTask(&task2);
555
556 if (NeedsExtendedTasks(test_name))
557 {
558 g_Kernel.AddTask(&task3);
559 g_Kernel.AddTask(&task4);
560 }
561
562 g_Kernel.Start();
563
565
566 printf("Result: %s\n", result == TestContext::SUCCESS_EXIT_CODE ? "PASS" : "FAIL");
567 printf("--------------\n");
568
569 return result;
570}
571
575int main(int argc, char **argv)
576{
577 (void)argc;
578 (void)argv;
579
580 using namespace stk::test::spinlock;
581
583
584 int total_failures = 0, total_success = 0;
585
586 printf("--------------\n");
587
588 g_Kernel.Initialize();
589
590#ifndef __ARM_ARCH_6M__
591
592 // Test 1: Lock/Unlock provides mutual exclusion under contention
594 total_failures++;
595 else
596 total_success++;
597
598 // Test 2: TryLock() succeeds on a free lock; fails while another task holds it (tasks 0-2 only)
600 total_failures++;
601 else
602 total_success++;
603
604 // Test 3: TryLock() returns false immediately when lock is held by another task (tasks 0-2 only)
606 total_failures++;
607 else
608 total_success++;
609
610 // Test 4: Recursive Lock() does not deadlock; fully released only after matching Unlock() calls
612 total_failures++;
613 else
614 total_success++;
615
616 // Test 5: TryLock() recursive for owner; correctly fails for non-owner (tasks 0-2 only)
618 total_failures++;
619 else
620 total_success++;
621
622 // Test 6: Lock() yields cooperatively when spin count exhausted; no livelock
624 total_failures++;
625 else
626 total_success++;
627
628 // Test 7: Unlock() transfers to a waiting task; no lost or duplicate increments
630 total_failures++;
631 else
632 total_success++;
633
634#endif // __ARM_ARCH_6M__
635
636 // Test 8: Stress test mixing blocking Lock() and non-blocking TryLock()
638 total_failures++;
639 else
640 total_success++;
641
642 int32_t final_result = (total_failures == 0 ? TestContext::SUCCESS_EXIT_CODE : TestContext::DEFAULT_FAILURE_EXIT_CODE);
643
644 printf("##############\n");
645 printf("Total tests: %d\n", total_failures + total_success);
646 printf("Failures: %d\n", total_failures);
647
649 return final_result;
650}
Top-level STK include. Provides the Kernel class template and all built-in task-switching strategies.
Implementation of synchronization primitive: stk::sync::SpinLock.
static int32_t RunTest(const char *test_name, int32_t param=0)
#define STK_TASK
#define _STK_SL_TEST_TASKS_MAX
int main(int argc, char **argv)
static int32_t RunTest(const char *test_name, int32_t param=0)
#define _STK_SL_TEST_LONG_SLEEP
#define _STK_SL_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
Namespace of the test inventory.
Namespace of SpinLock test.
static volatile int32_t g_SharedCounter
static void ResetTestState()
static volatile bool g_TestComplete
static volatile int32_t g_TestResult
static volatile int32_t g_InstancesDone
static Kernel< KERNEL_DYNAMIC, 5, SwitchStrategyRR, PlatformDefault > g_Kernel
static volatile int32_t g_OrderIndex
static sync::SpinLock g_TestSpinLock
Concrete implementation of IKernel.
Definition stk.h:83
Task(const Task &)=delete
Recursive spinlock.
Tests that Lock()/Unlock() provides mutual exclusion under concurrent access.
MutualExclusionTask(uint8_t task_id, int32_t iterations)
void Run()
Entry point of the user task.
Tests TryLock() succeeds immediately when the lock is free.
TryLockFreeTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
Tests TryLock() returns false immediately when the lock is held by another task.
TryLockContendedTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
Tests recursive Lock()/Unlock() by the same task.
RecursiveLockTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
Tests TryLock() recursive acquisition by the owning task.
RecursiveTryLockTask(uint8_t task_id, int32_t)
void Run()
Entry point of the user task.
Tests that Lock() yields cooperatively when spin count is exhausted.
void Run()
Entry point of the user task.
YieldUnderContentionTask(uint8_t task_id, int32_t iterations)
Tests that Unlock() correctly transfers the lock to a waiting contender.
void Run()
Entry point of the user task.
UnlockTransferTask(uint8_t task_id, int32_t iterations)
Stress test mixing Lock() and TryLock() 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.