Skyward boardcore
Loading...
Searching...
No Matches
TaskScheduler.cpp
Go to the documentation of this file.
1/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
2 * Authors: Alain Carlucci, Federico Terraneo, Matteo Piazzolla
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#include "TaskScheduler.h"
24
26#include <utils/TimeUtils.h>
27
28#include <algorithm>
29#include <mutex>
30
31using namespace std;
32using namespace std::chrono;
33using namespace miosix;
34
35namespace Boardcore
36{
37
38TaskScheduler::EventQueue TaskScheduler::makeAgenda()
39{
40 std::vector<Event> agendaStorage{};
41 agendaStorage.reserve(MAX_TASKS);
42 return EventQueue{std::greater<Event>{}, std::move(agendaStorage)};
43}
44
45TaskScheduler::TaskScheduler(miosix::Priority priority)
46 : ActiveObject(STACK_MIN_FOR_SKYWARD, priority), tasks(),
47 agenda(makeAgenda())
48{
49 // Preallocate the vector to avoid dynamic allocation later on
50 tasks.reserve(MAX_TASKS);
51
52 // Reserve the zeroth element so that the task ID is never zero, for
53 // API compatibility: returned value from addTask() is zero on error.
54 tasks.emplace_back();
55}
56
57size_t TaskScheduler::addTask(function_t function, nanoseconds period,
58 Policy policy, time_point<steady_clock> startTime)
59{
60 std::unique_lock<miosix::FastMutex> lock{mutex};
61
62 if (tasks.size() >= MAX_TASKS)
63 {
64 // Unlock the mutex to release the scheduler resources before logging
65 lock.unlock();
66 LOG_ERR(logger, "Full task scheduler");
67 return 0;
68 }
69
70 if (policy == Policy::ONE_SHOT)
71 startTime += period;
72
73 // Insert a new task with the given parameters
74 tasks.emplace_back(function, period.count(), policy,
75 startTime.time_since_epoch().count());
76 size_t id = tasks.size() - 1;
77
78 // Only add the task to the agenda if the scheduler is running
79 // Otherwise, the agenda will be populated when the scheduler is started
80 if (isRunning())
81 agenda.emplace(id, startTime.time_since_epoch().count());
82 condvar.broadcast(); // Signals the run thread
83
84 return id;
85}
86
88{
89 std::unique_lock<miosix::FastMutex> lock{mutex};
90
91 if (id > tasks.size() - 1)
92 {
93 lock.unlock();
94 LOG_ERR(logger, "Tried to enable an out-of-range task, id = {}", id);
95 return;
96 }
97
98 Task& task = tasks[id];
99
100 // Check that the task function is not empty
101 // Attempting to run an empty function will throw a bad_function_call
102 // exception
103 if (task.empty())
104 {
105 lock.unlock();
106 LOG_WARN(logger, "Tried to enable an empty task, id = {}", id);
107 return;
108 }
109
110 task.enabled = true;
111 agenda.emplace(id, miosix::getTime() + task.period);
112}
113
115{
116 std::unique_lock<miosix::FastMutex> lock{mutex};
117
118 if (id > tasks.size() - 1)
119 {
120 lock.unlock();
121 LOG_ERR(logger, "Tried to disable an out-of-range task, id = {}", id);
122 return;
123 }
124
125 Task& task = tasks[id];
126 task.enabled = false;
127 // Reset the last call time to avoid incorrect period statistics
128 task.lastCall = -1;
129}
130
132{
133 Lock<FastMutex> lock(mutex);
134
135 // This check is necessary to prevent task normalization if the scheduler is
136 // already stopped
137 if (running)
138 return false;
139
140 // Populate the agenda with the tasks we have so far
141 populateAgenda();
142
143 return ActiveObject::start();
144}
145
147{
148 stopFlag = true; // Signal the run function to stop
149 condvar.broadcast(); // Wake the run function even if there are no tasks
150
152}
153
154vector<TaskStatsResult> TaskScheduler::getTaskStats()
155{
156 Lock<FastMutex> lock(mutex);
157
158 vector<TaskStatsResult> result;
159
160 for (size_t id = 1; id < tasks.size(); id++)
161 {
162 const Task& task = tasks[id];
163 if (task.enabled)
164 result.push_back(fromTaskIdPairToStatsResult(task, id));
165 }
166
167 return result;
168}
169
170void TaskScheduler::populateAgenda()
171{
172 int64_t currentTime = miosix::getTime();
173
174 for (size_t id = 1; id < tasks.size(); id++)
175 {
176 Task& task = tasks[id];
177 int64_t startTime = task.startTime;
178
179 // Shift the task's start time if it precedes the current time
180 // to avoid clumping all tasks at the beginning (see issue #91)
181 if (startTime < currentTime)
182 {
183 int64_t timeSinceStart = currentTime - startTime;
184 int64_t periodsMissed = timeSinceStart / task.period;
185 int64_t periodsToSkip = periodsMissed + 1;
186 startTime += periodsToSkip * task.period;
187 }
188
189 agenda.emplace(id, startTime);
190 }
191}
192
193void TaskScheduler::run()
194{
195 Lock<FastMutex> lock(mutex);
196
197 while (true)
198 {
199 while (agenda.empty() && !shouldStop())
200 condvar.wait(mutex);
201
202 // Exit if the ActiveObject has been stopped
203 if (shouldStop())
204 return;
205
206 int64_t startTime = miosix::getTime();
207 Event nextEvent = agenda.top();
208
209 if (nextEvent.nextTime <= startTime)
210 {
211 Task& nextTask = tasks[nextEvent.taskId];
212 agenda.pop();
213
214 // Execute the task function
215 if (nextTask.enabled)
216 {
217 {
218 Unlock<FastMutex> unlock(lock);
219
220 try
221 {
222 nextTask.function();
223 }
224 catch (...)
225 {
226 // Update the failed statistic
227 nextTask.failedEvents++;
228 }
229 }
230
231 // Enqueue only on a valid task
232 updateStats(nextEvent, startTime, miosix::getTime());
233 enqueue(nextEvent, startTime);
234 }
235 }
236 else
237 {
238 Unlock<FastMutex> unlock(lock);
239
240 Thread::nanoSleepUntil(nextEvent.nextTime);
241 }
242 }
243}
244
245void TaskScheduler::updateStats(const Event& event, int64_t startTime,
246 int64_t endTime)
247{
248 Task& task = tasks[event.taskId];
249
250 float activationTime = startTime - event.nextTime;
251 task.activationStats.add(activationTime / Constants::NS_IN_MS);
252
253 int64_t lastCall = task.lastCall;
254 if (lastCall >= 0)
255 {
256 float periodTime = startTime - lastCall;
257 task.periodStats.add(periodTime / Constants::NS_IN_MS);
258 }
259 // Update the last call time to the current start time for the next
260 // iteration
261 task.lastCall = startTime;
262
263 float workloadTime = endTime - startTime;
264 task.workloadStats.add(workloadTime / Constants::NS_IN_MS);
265}
266
267void TaskScheduler::enqueue(Event event, int64_t startTime)
268{
269 Task& task = tasks[event.taskId];
270 switch (task.policy)
271 {
272 case Policy::ONE_SHOT:
273 // If the task is one shot we won't push it to the agenda and we'll
274 // remove it from the tasks map.
275 task.enabled = false;
276 return;
277 case Policy::SKIP:
278 {
279 // Compute the number of missed periods since the last execution
280 int64_t timeSinceLastExec = startTime - event.nextTime;
281 int64_t periodsMissed = timeSinceLastExec / task.period;
282
283 // Schedule the task executon to the next aligned period, by
284 // skipping over the missed ones
285 // E.g. 3 periods have passed since last execution, the next viable
286 // schedule time is after 4 periods
287 int64_t periodsToSkip = periodsMissed + 1;
288 // Update the task to run at the next viable timeslot, while still
289 // being aligned to the original one
290 event.nextTime += periodsToSkip * task.period;
291
292 // Updated the missed events count
293 task.missedEvents += static_cast<uint32_t>(periodsMissed);
294 break;
295 }
296 case Policy::RECOVER:
297 event.nextTime += task.period;
298 break;
299 }
300
301 // Re-enqueue the event in the agenda and signals the run thread
302 agenda.push(event);
303 condvar.broadcast();
304}
305
306TaskScheduler::Task::Task()
307 : function(nullptr), period(0), startTime(0), enabled(false),
308 policy(Policy::SKIP), lastCall(-1), activationStats(), periodStats(),
309 workloadStats(), missedEvents(0), failedEvents(0)
310{
311}
312
313TaskScheduler::Task::Task(function_t function, int64_t period, Policy policy,
314 int64_t startTime)
315 : function(function), period(period), startTime(startTime), enabled(true),
316 policy(policy), lastCall(-1), activationStats(), periodStats(),
317 workloadStats(), missedEvents(0), failedEvents(0)
318{
319}
320
321} // namespace Boardcore
#define LOG_WARN(logger,...)
#define LOG_ERR(logger,...)
std::atomic< bool > stopFlag
std::atomic< bool > running
bool shouldStop()
Tells whether or not the ActiveObject should stop its execution.
virtual void stop()
Signals the runner thread to terminate and joins the thread.
virtual bool start()
Start the thread associated with this active object.
std::vector< TaskStatsResult > getTaskStats()
void enableTask(size_t id)
Enables the task with the given id.
bool start() override
Start the thread associated with this active object.
void disableTask(size_t id)
Disables the task with the given id, preventing it from executing.
@ ONE_SHOT
Run the task one single timer.
@ RECOVER
Prioritize the number of executions over the period.
void stop() override
Signals the runner thread to terminate and joins the thread.
size_t addTask(function_t function, uint32_t periodMs, Policy policy=Policy::RECOVER, int64_t startTick=Kernel::getOldTick())
Add a millisecond-period task function to the scheduler with an auto generated ID.
TaskScheduler(miosix::Priority priority=miosix::PRIORITY_MAX - 1)
static constexpr size_t MAX_TASKS
The maximum number of tasks the scheduler can handle.
constexpr long long NS_IN_MS
Definition TimeUtils.h:30
This file includes all the types the logdecoder script will decode.
uint8_t Event
Definition Event.h:30
Definition WIZ5500.h:318