FastLED 3.10.6
Loading...
Searching...
No Matches
AutoResearchRpConcurrency.cpp
Go to the documentation of this file.
2
3#include "platforms/is_platform.h"
4
5#if defined(FL_IS_RP)
6
7#include <Arduino.h>
8
9#include "fl/stl/atomic.h"
10#include "fl/stl/mutex.h"
11#include "fl/stl/semaphore.h"
13
14// Arduino-Pico otherwise splits one 8 KB stack into two 4 KB halves when
15// setup1()/loop1() are present. The RPC/JSON call chain on core 0 can exceed
16// its half and overwrite core 1 while this diagnostic is running.
17bool core1_separate_stack = true;
18
19namespace {
20
21constexpr int kRpConcurrencyIterations = 100;
22constexpr uint32_t kRpConcurrencyDeadlineMs = 1500;
23constexpr uint32_t kRpCore1ReadyTimeoutMs = 500;
24constexpr uint32_t kRpCore1DoneTimeoutMs = 2000;
25// AutoResearch's loop watchdog is 5 seconds. Leave a full second for JSON
26// response construction and transport after every bounded diagnostic wait.
27constexpr uint32_t kRpConcurrencyWatchdogBudgetMs = 4000;
28FL_STATIC_ASSERT(kRpConcurrencyDeadlineMs + kRpCore1ReadyTimeoutMs +
29 kRpCore1DoneTimeoutMs <=
30 kRpConcurrencyWatchdogBudgetMs,
31 "RP concurrency diagnostics exceed the watchdog-safe budget");
32
33fl::mutex gRpCounterMutex;
34fl::binary_semaphore gRpCore1Request(0);
35fl::counting_semaphore<1000> gRpCore1Done(0);
36fl::atomic<bool> gRpCore1Ready(false);
37fl::atomic<bool> gRpRequestInFlight(false);
38fl::atomic<uint32_t> gRpCore1LoopCount(0);
39fl::atomic<uint32_t> gRpRequestGeneration(0);
40fl::atomic<uint32_t> gRpCompletedGeneration(0);
41fl::atomic<int> gRpCore1Iterations(0);
42fl::atomic<int> gRpCore1Completed(0);
43int gRpSharedCounter = 0;
44
45int incrementRpSharedCounter(int iterations) {
46 int completed = 0;
47 const uint32_t deadline = millis() + kRpConcurrencyDeadlineMs;
48 while (completed < iterations &&
49 static_cast<int32_t>(millis() - deadline) < 0) {
50 if ((completed & 1) != 0) {
51 if (!gRpCounterMutex.try_lock()) {
52 continue;
53 }
54 } else {
55 gRpCounterMutex.lock();
56 }
57 ++gRpSharedCounter;
58 gRpCounterMutex.unlock();
59 ++completed;
60 }
61 return completed;
62}
63
64} // namespace
65
66// Arduino-Pico runs these entry points on the RP's second physical core.
67void setup1() { gRpCore1Ready.store(true); }
68
69void loop1() {
70 gRpCore1LoopCount.fetch_add(1);
71 if (!gRpCore1Request.try_acquire()) {
72 // Avoid a hot atomic/spinlock loop starving shared RP bus resources
73 // while retaining an observable core-1 heartbeat.
74 delay(1);
75 return;
76 }
77
78 const uint32_t generation = gRpRequestGeneration.load();
79 gRpCore1Completed.store(incrementRpSharedCounter(gRpCore1Iterations.load()));
80 gRpCompletedGeneration.store(generation);
81 gRpRequestInFlight.store(false);
82 gRpCore1Done.release();
83}
84
85namespace autoresearch {
86
87fl::json runRpConcurrencyTest() {
88 fl::json response = fl::json::object();
89 response.set("supported", true);
90 response.set("backend", "pico-sdk-mutex+arduino-core1");
91
92 const uint32_t readyDeadline = millis() + kRpCore1ReadyTimeoutMs;
93 while (!gRpCore1Ready.load() && static_cast<int32_t>(millis() - readyDeadline) < 0) {
94 delay(1);
95 }
96 if (!gRpCore1Ready.load()) {
97 response.set("success", false);
98 response.set("error", "RP core1 did not start");
99 return response;
100 }
101
102 while (gRpCore1Done.try_acquire()) {}
103 if (gRpRequestInFlight.exchange(true)) {
104 response.set("success", false);
105 response.set("error", "RP core1 still owns the prior concurrency request");
106 return response;
107 }
108 if (!gRpCounterMutex.try_lock()) {
109 gRpRequestInFlight.store(false);
110 response.set("success", false);
111 response.set("error", "RP mutex unavailable before contention test");
112 return response;
113 }
114 gRpSharedCounter = 0;
115 gRpCounterMutex.unlock();
116
117 gRpCore1Iterations.store(kRpConcurrencyIterations);
118 gRpCore1Completed.store(0);
119 const uint32_t generation = gRpRequestGeneration.fetch_add(1) + 1;
120 const uint32_t core1LoopCountBefore = gRpCore1LoopCount.load();
121 gRpCore1Request.release();
122 const int core0Completed =
123 incrementRpSharedCounter(kRpConcurrencyIterations);
124
125 bool core1Done = false;
126 const uint32_t doneDeadline = millis() + kRpCore1DoneTimeoutMs;
127 while (static_cast<int32_t>(millis() - doneDeadline) < 0) {
128 if (gRpCore1Done.try_acquire()) {
129 if (gRpCompletedGeneration.load() == generation) {
130 core1Done = true;
131 break;
132 }
133 }
134 delay(1);
135 }
136
137 if (!gRpCounterMutex.try_lock()) {
138 response.set("success", false);
139 response.set("error", "RP mutex unavailable after contention test");
140 return response;
141 }
142 const int actual = gRpSharedCounter;
143 gRpCounterMutex.unlock();
144 const int core1Completed = gRpCore1Completed.load();
145 const uint32_t core1LoopCountAfter = gRpCore1LoopCount.load();
146 fl::recursive_mutex recursiveMutex;
147 recursiveMutex.lock();
148 const bool recursiveMutexReady = recursiveMutex.try_lock();
149 if (recursiveMutexReady) {
150 recursiveMutex.unlock();
151 }
152 recursiveMutex.unlock();
153 const int expected = kRpConcurrencyIterations * 2;
154 response.set("success", core1Done &&
155 core0Completed == kRpConcurrencyIterations &&
156 core1Completed == kRpConcurrencyIterations &&
157 actual == expected && recursiveMutexReady);
158 response.set("core1Ready", gRpCore1Ready.load());
159 response.set("core1Done", core1Done);
160 response.set("iterationsPerCore", static_cast<int64_t>(kRpConcurrencyIterations));
161 response.set("core0Completed", static_cast<int64_t>(core0Completed));
162 response.set("core1Completed", static_cast<int64_t>(core1Completed));
163 response.set("core1LoopCountBefore", static_cast<int64_t>(core1LoopCountBefore));
164 response.set("core1LoopCountAfter", static_cast<int64_t>(core1LoopCountAfter));
165 response.set("generation", static_cast<int64_t>(generation));
166 response.set("recursiveMutexReady", recursiveMutexReady);
167 response.set("expected", static_cast<int64_t>(expected));
168 response.set("actual", static_cast<int64_t>(actual));
169 return response;
170}
171
172} // namespace autoresearch
173
174#else
175
176namespace autoresearch {
177
179 fl::json response = fl::json::object();
180 response.set("success", false);
181 response.set("supported", false);
182 response.set("backend", "unsupported");
183 response.set("reason", "RP dual-core concurrency is available only on RP2xxx");
184 return response;
185}
186
187} // namespace autoresearch
188
189#endif
static json object() FL_NO_EXCEPT
Definition json.h:857
Platform-independent mutex interface.
fl::json runRpConcurrencyTest()
Exercise the RP dual-core mutex and semaphore backends from both cores.
void delay(u32 ms, bool run_async=true) FL_NO_EXCEPT
Public delay wrapper that keeps bare Arduino delay() preferred after using fl::delay; while still all...
Definition delay.h:98
AtomicFake< T > atomic
Definition atomic.h:26
fl::u32 millis()
Universal millisecond timer - returns milliseconds since system startup.
fl::platforms::recursive_mutex recursive_mutex
Definition mutex.h:21
fl::platforms::counting_semaphore< LeastMaxValue > counting_semaphore
Counting semaphore abstraction for FastLED.
Definition semaphore.h:28
fl::platforms::binary_semaphore binary_semaphore
Binary semaphore abstraction for FastLED.
Definition semaphore.h:40
fl::platforms::mutex mutex
Definition mutex.h:20
#define FL_STATIC_ASSERT(...)
Portable compile-time assertion wrapper.
fl::u32 uint32_t
Definition stdint.h:218
fl::i32 int32_t
Definition stdint.h:219