rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
event_queue.cpp
Go to the documentation of this file.
1/**
2 * @file event_queue.cpp
3 * This is a data structure which keeps track of all pending events
4 * Implemented as a linked list, which is fine since the number of
5 * pending events is pretty low
6 * todo: MAYBE migrate to a better data structure, but that's low priority
7 *
8 * this data structure is NOT thread safe
9 *
10 * @date Apr 17, 2014
11 * @author Andrey Belomutskiy, (c) 2012-2020
12 */
13
14#include "pch.h"
15
16#include "event_queue.h"
17#include "efitime.h"
18
19#ifndef EFI_UNIT_TEST_VERBOSE_ACTION
20#define EFI_UNIT_TEST_VERBOSE_ACTION 0
21#elif EFI_UNIT_TEST_VERBOSE_ACTION
22#include <iostream>
23#endif
24
25#if EFI_UNIT_TEST
26extern bool verboseMode;
27#endif /* EFI_UNIT_TEST */
28
30 : m_lateDelay(lateDelay)
31{
32 for (size_t i = 0; i < efi::size(m_pool); i++) {
34 }
35
36#if EFI_PROD_CODE
38#endif
39}
40
42 auto retVal = m_freelist;
43
44 if (retVal) {
46 retVal->nextScheduling_s = nullptr;
47
48#if EFI_PROD_CODE
50#endif
51 }
52
53 return retVal;
54}
55
57 // Only return this scheduling to the free list if it's from the correct pool
58 if (sched >= &m_pool[0] && sched <= &m_pool[efi::size(m_pool) - 1]) {
60 m_freelist = sched;
61
62#if EFI_PROD_CODE
64#endif
65 }
66}
67
68/**
69 * @return true if inserted into the head of the list
70 */
71bool EventQueue::insertTask(scheduling_s *scheduling, efitick_t timeNt, action_s const& action) {
73
74 if (!scheduling) {
75 scheduling = getFreeScheduling();
76
77 // If still null, the free list is empty and all schedulings in the pool have been expended.
78 if (!scheduling) {
79 // TODO: should we warn or error here?
80// todo: look into why units tests fail here
81#if EFI_PROD_CODE
82 criticalError("No slots in scheduling pool");
83#endif
84 return false;
85 }
86 }
87
89 efiAssert(ObdCode::CUSTOM_ERR_ASSERT, action.getCallback() != NULL, "NULL callback", false);
90
91// please note that simulator does not use this code at all - simulator uses signal_executor_sleep
92
93 if (scheduling->action) {
94#if EFI_UNIT_TEST
95 if (verboseMode) {
96 printf("Already scheduled was %d\r\n", (int)scheduling->getMomentNt());
97 printf("Already scheduled now %d\r\n", (int)timeNt);
98 }
99#endif /* EFI_UNIT_TEST */
100 return false;
101 }
102
103 scheduling->setMomentNt(timeNt);
104 scheduling->action = action;
105
106 if (!m_head || timeNt < m_head->getMomentNt()) {
107 // here we insert into head of the linked list
108 LL_PREPEND2(m_head, scheduling, nextScheduling_s);
110 return true;
111 } else {
112 // here we know we are not in the head of the list, let's find the position - linear search
113 scheduling_s *insertPosition = m_head;
114 while (insertPosition->nextScheduling_s != NULL && insertPosition->nextScheduling_s->getMomentNt() < timeNt) {
115 insertPosition = insertPosition->nextScheduling_s;
116 }
117
118 scheduling->nextScheduling_s = insertPosition->nextScheduling_s;
119 insertPosition->nextScheduling_s = scheduling;
121 return false;
122 }
123}
124
127
128 // Special case: event isn't scheduled, so don't cancel it
129 if (!scheduling->action) {
130 return;
131 }
132
133 // Special case: empty list, nothing to do
134 if (!m_head) {
135 return;
136 }
137
138 // Special case: is the item to remove at the head?
139 if (scheduling == m_head) {
141 scheduling->nextScheduling_s = nullptr;
142 scheduling->action = {};
143 } else {
144 auto prev = m_head; // keep track of the element before the one to remove, so we can link around it
145 auto current = prev->nextScheduling_s;
146
147 // Find our element
148 while (current && current != scheduling) {
149 prev = current;
150 current = current->nextScheduling_s;
151 }
152
153 // Walked off the end, this is an error since this *should* have been scheduled
154 if (!current) {
155 firmwareError(ObdCode::OBD_PCM_Processor_Fault, "EventQueue::remove didn't find element");
156 return;
157 }
158
159 efiAssertVoid(ObdCode::OBD_PCM_Processor_Fault, current == scheduling, "current not equal to scheduling");
160
161 // Link around the removed item
162 prev->nextScheduling_s = current->nextScheduling_s;
163
164 // Clean the item to remove
165 current->nextScheduling_s = nullptr;
166 current->action = {};
167 }
168
170}
171
172/**
173 * On this layer it does not matter which units are used - us, ms ot nt.
174 *
175 * This method is always invoked under a lock
176 * @return Get the timestamp of the soonest pending action, skipping all the actions in the past
177 */
178expected<efitick_t> EventQueue::getNextEventTime(efitick_t nowNt) const {
179 if (m_head) {
180 if (m_head->getMomentNt() <= nowNt) {
181 /**
182 * We are here if action timestamp is in the past. We should rarely be here since this 'getNextEventTime()' is
183 * always invoked by 'scheduleTimerCallback' which is always invoked right after 'executeAllPendingActions' - but still,
184 * for events which are really close to each other we would end up here.
185 *
186 * looks like we end up here after 'writeconfig' (which freezes the firmware) - we are late
187 * for the next scheduled event
188 */
189 return nowNt + m_lateDelay;
190 } else {
191 return m_head->getMomentNt();
192 }
193 }
194
195 return unexpected;
196}
197
198/**
199 * See also maxPrecisionCallbackDuration for total hw callback time
200 */
202
203/**
204 * Invoke all pending actions prior to specified timestamp
205 * @return number of executed actions
206 */
207int EventQueue::executeAll(efitick_t now) {
209
210 int executionCounter = 0;
211
213
214 bool didExecute;
215 do {
216 didExecute = executeOne(now);
217 executionCounter += didExecute ? 1 : 0;
218 } while (didExecute);
219
220 return executionCounter;
221}
222
223bool EventQueue::executeOne(efitick_t now) {
224 // Read the head every time - a previously executed event could
225 // have inserted something new at the head
226 scheduling_s* current = m_head;
227
228 // Queue is empty - bail
229 if (!current) {
230 return false;
231 }
232
233 // If the next event is far in the future, we'll reschedule
234 // and execute it next time.
235 // We do this when the next event is close enough that the overhead of
236 // resetting the timer and scheduling an new interrupt is greater than just
237 // waiting for the time to arrive. On current CPUs, this is reasonable to set
238 // around 10 microseconds.
239 if (current->getMomentNt() > now + m_lateDelay) {
240 return false;
241 }
242
243#if EFI_UNIT_TEST
244// efitick_t spinDuration = current->getMomentNt() - getTimeNowNt();
245// if (spinDuration > 0) {
246// throw std::runtime_error("Time Spin in unit test");
247// }
248#endif
249
250 // near future - spin wait for the event to happen and avoid the
251 // overhead of rescheduling the timer.
252 // yes, that's a busy wait but that's what we need here
253 while (current->getMomentNt() > getTimeNowNt()) {
254#if EFI_UNIT_TEST
255 // todo: remove this hack see https://github.com/rusefi/rusefi/issues/6457
256extern bool unitTestBusyWaitHack;
257 if (unitTestBusyWaitHack) {
258 break;
259 }
260#endif
261 UNIT_TEST_BUSY_WAIT_CALLBACK();
262 }
263
264 // step the head forward, unlink this element, clear scheduled flag
265 m_head = current->nextScheduling_s;
266 current->nextScheduling_s = nullptr;
267
268 // Grab the action but clear it in the event so we can reschedule from the action's execution
269 auto const action{ std::move(current->action) };
270
271 tryReturnScheduling(current);
272 current = nullptr;
273
274#if EFI_DETAILED_LOGGING
275 printf("QUEUE: execute current=%d param=%d\r\n", reinterpret_cast<uintptr_t>(current), action.getArgumentRaw());
276#endif
277
278 // Execute the current element
279 {
281#if EFI_DETAILED_LOGGING && EFI_UNIT_TEST_VERBOSE_ACTION
282 std::cout << "EventQueue::executeOne: " << action.getCallbackName() << "(" << reinterpret_cast<uintptr_t>(action.getCallback()) << ") with raw arg = " << action.getArgumentRaw() << std::endl;
283#endif
284 action.execute();
285
286#if EFI_UNIT_TEST
287 // std::cout << "Executed at " << now << std::endl;
288#endif
289 }
290
292 return true;
293}
294
295int EventQueue::size() const {
296 scheduling_s *tmp;
297 int result;
298 LL_COUNT2(m_head, tmp, result, nextScheduling_s);
299 return result;
300}
301
303#if EFI_UNIT_TEST || EFI_SIMULATOR
304 int counter = 0;
305 scheduling_s *current = m_head;
306 while (current != NULL && current->nextScheduling_s != NULL) {
307 efiAssertVoid(ObdCode::CUSTOM_ERR_6623, current->getMomentNt() <= current->nextScheduling_s->getMomentNt(), "list order");
308 current = current->nextScheduling_s;
309 if (counter++ > 1'000'000'000)
310 criticalError("EventQueue: looks like a loop?!");
311 }
312#endif // EFI_UNIT_TEST || EFI_SIMULATOR
313}
314
318
319// todo: reduce code duplication with another 'getElementAtIndexForUnitText'
321 scheduling_s * current;
322
323 LL_FOREACH2(m_head, current, nextScheduling_s)
324 {
325 if (index == 0) {
326 return current;
327 }
328 index--;
329 }
330
331 return NULL;
332}
333
335 // Flush the queue, resetting all scheduling_s as though we'd executed them
336 while(m_head) {
337 auto x = m_head;
338 // link next element to head
340
341 // Reset this element
342 x->setMomentNt(0);
343 x->nextScheduling_s = nullptr;
344 x->action = {};
345 }
346
347 m_head = nullptr;
348}
void assertListIsSorted() const
const efidur_t m_lateDelay
Definition event_queue.h:49
scheduling_s * m_head
Definition event_queue.h:48
void tryReturnScheduling(scheduling_s *sched)
scheduling_s * m_freelist
Definition event_queue.h:51
scheduling_s * getElementAtIndexForUnitText(int index)
int executeAll(efitick_t now)
bool insertTask(scheduling_s *scheduling, efitick_t timeX, action_s const &action)
int size() const
scheduling_s m_pool[64]
Definition event_queue.h:52
expected< efitick_t > getNextEventTime(efitick_t nowUs) const
scheduling_s * getHead()
scheduling_s * getFreeScheduling()
bool executeOne(efitick_t now)
EventQueue(efidur_t lateDelay=0)
void remove(scheduling_s *scheduling)
constexpr schfunc_t getCallback() const
Definition scheduler.h:248
efitick_t getTimeNowNt()
Definition efitime.cpp:19
static WrapAround62 timeNt
Definition efitime.cpp:14
void firmwareError(ObdCode code, const char *fmt,...)
bool verboseMode
uint32_t maxEventCallbackDuration
@ OBD_PCM_Processor_Fault
@ CUSTOM_ERR_ASSERT
@ CUSTOM_ERR_6623
@ EventQueueExecuteAll
@ EventQueueInsertTask
@ EventQueueExecuteCallback
efitick_t efidur_t
bool verboseMode
scheduling_s * nextScheduling_s
Definition scheduler.h:294
efitick_t getMomentNt() const
Definition scheduler.h:270
action_s action
Definition scheduler.h:296
void setMomentNt(efitick_t p_moment)
Definition scheduler.h:284
TunerStudioOutputChannels * getTunerStudioOutputChannels()
Definition engine.cpp:581
printf("\n")