Skip to content
Snippets Groups Projects
Select Git revision
  • lsm6-usart
  • main default protected
  • zvk-dev
  • sensor-manager-upd
  • roccaraso-arp-fix
  • hil-transceiver-dma
  • bfloat16
  • usart-debug
  • arp-gyro
  • async-fsm
  • chipselect-mux
  • nas-catch-dev
  • mockup-main-software
  • quadspi-flash
  • quadspi-flash2
  • sx1278-resilience
  • units-impl
  • nokia-tm-dev
  • spi
  • cc3135
  • PYXIS_ROCCARASO
  • PYXIS_EUROC
  • lynx-euroc
  • hermes-v1.0
  • hermes-flight-1
25 results

TaskScheduler.h

Blame
  • TaskScheduler.h 8.04 KiB
    /* Copyright (c) 2015-2016 Skyward Experimental Rocketry
     * Authors: Alain Carlucci, Federico Terraneo, Matteo Piazzolla
     *
     * Permission is hereby granted, free of charge, to any person obtaining a copy
     * of this software and associated documentation files (the "Software"), to deal
     * in the Software without restriction, including without limitation the rights
     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     * copies of the Software, and to permit persons to whom the Software is
     * furnished to do so, subject to the following conditions:
     *
     * The above copyright notice and this permission notice shall be included in
     * all copies or substantial portions of the Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     * THE SOFTWARE.
     */
    
    #pragma once
    
    #include <ActiveObject.h>
    #include <Singleton.h>
    #include <debug/debug.h>
    #include <diagnostic/PrintLogger.h>
    #include <utils/Stats/Stats.h>
    
    #include <cstdint>
    #include <list>
    #include <map>
    #include <queue>
    
    #include "TaskSchedulerData.h"
    
    namespace Boardcore
    {
    
    /**
     * @brief The Task Scheduler allow to manage simple tasks with a single thread.
     * All the task added must not take more than 1ms to execute and should take
     * less time as possible to ensure other tasks are executed as they are supposed
     * to.
     *
     * HOW TO USE THE TASK SCHEDULER
     *
     * TaskScheduler.add(nonblocking_std::function_without_sleeps, millisec, id);
     * and.. it works like magic. :)
     *
     * Example:
     *    void magic_std::function() {
     *        // do something NONBLOCKING and WITHOUT SLEEPS
     *    }
     *    TaskScheduler.add(magic_std::function, 150);
     *
     */
    class TaskScheduler : public ActiveObject
    {
    public:
        typedef std::function<void()> function_t;
    
        /**
         * @brief It defines the tasks array maximum size
         */
        static constexpr size_t TASKS_SIZE = 256;
    
        /**
         * @brief Task behavior policy.
         *
         * This policies allows to change the behavior of the scheduler for the
         * specific task:
         * - ONE_SHOT: Allows to run the task once. When it is executed, it is
         * removed from the tasks list.
         * - SKIP: If for whatever reason a task can't be executed when
         * it is supposed to (e.g. another thread occupies the CPU), the scheduler
         * doesn't recover the missed executions but instead skips those and
         * continues normally. This ensures that all the events are aligned with
         * the original start tick. In other words, the period and the start tick of
         * a task specifies the time slots the task has to be executed. If one of
         * this time slots can't be used, that specific execution won't be
         * recovered.
         * - RECOVER: On the other hand, the RECOVER policy ensures that the missed
         * executions are run. However, this will cause the period to not be
         * respected and the task will run consecutively for some time (See issue
         * #91).
         */
        enum class Policy
        {
            ONE_SHOT,  ///< Run the task one single timer.
            SKIP,      // Skips lost executions and stays aligned with the original
                       // start tick.
            RECOVER    ///< Prioritize the number of executions over the period.
        };
    
        TaskScheduler();
    
        /**
         * @brief Add a task function to the scheduler with an auto generated id.
         *
         * Note that each task has it's own unique ID, even one shot tasks!
         * Therefore, if a task already exists with the same id, the function will
         * fail and return false.
         *
         * For one shot tasks, the period is used as a delay. If 0 the task will be
         * executed immediately, otherwise after the given period.
         *
         * @param function Function to be called periodically.
         * @param period Inter call period.
         * @param policy Task policy, default is SKIP.
         * @param startTick First activation time, useful for synchronizing tasks.
         * @return true if the task was added successfully.
         */
        size_t addTask(function_t function, uint32_t period,
                       Policy policy     = Policy::SKIP,
                       int64_t startTick = miosix::getTick());
    
        /**
         * @brief Removes the task identified by the given id if it exists.
         *
         * @param id Id of the task to remove.
         * @return true if the task was removed.
         */
        bool removeTask(uint8_t id);
    
        bool start() override;
    
        void stop() override;
    
        std::vector<TaskStatsResult> getTaskStats();
    
    private:
        /**
         * @brief Check the start time of the tasks in the agenda and moves them in
         * the future respecting the period in respect to the original start time.
         */
        void normalizeTasks();
    
        struct Task
        {
            function_t function;
            uint32_t period;
            size_t id;
            bool valid;
            Policy policy;
            int64_t lastCall;  ///< Last activation tick for statistics computation.
            Stats activationStats;  ///< Stats about activation tick error.
            Stats periodStats;      ///< Stats about period error.
            Stats workloadStats;    ///< Stats about time the task takes to compute.
            uint32_t missedEvents;  ///< Number of events that could not be run.
            uint32_t failedEvents;  ///< Number of events ended with exceptions.
        };
    
        struct Event
        {
            Task* task;        ///< The task to execute.
            int64_t nextTick;  ///< Tick of next activation.
    
            bool operator<(const Event& e) const
            {
                // Note: operator < is reversed, so that the priority_queue will
                // return the lowest element first
                return this->nextTick > e.nextTick;
            }
        };
    
        void run() override;
    
        /**
         * @brief Update task statistics (Intended for when the task is executed).
         *
         * This function changes the task last call tick to the startTick.
         *
         * \param event Current event.
         * \param startTick Start of execution tick.
         * \param endTick End of execution tick.
         */
        void updateStats(const Event& event, int64_t startTick, int64_t endTick);
    
        /**
         * @brief (Re)Enqueue an event into the agenda based on the scheduling
         * policy.
         *
         * Requires the mutex to be locked!
         *
         * \param event Event to be scheduled. Note: this parameter is modified, the
         * nextTick field is updated in order to respect the task interval.
         * \param startTick Activation tick, needed to update the nextTick value of
         * the event.
         */
        void enqueue(Event& event, int64_t startTick);
    
        /**
         * @brief Creates a task with the passed values
         *
         * @param function The std::function to be called
         * @param period The Period in [ms]
         * @param id The task intrinsic id
         * @param validity The validity of the task (false if not initialized or if
         * removed)
         * @param policy The task policy in case of a miss
         */
        Task makeTask(function_t function, uint32_t period, size_t id,
                      bool validity, Policy policy);
    
        static TaskStatsResult fromTaskIdPairToStatsResult(Task task)
        {
    
            return TaskStatsResult{task.id,
                                   task.period,
                                   task.activationStats.getStats(),
                                   task.periodStats.getStats(),
                                   task.workloadStats.getStats(),
                                   task.missedEvents,
                                   task.failedEvents};
        }
    
        miosix::FastMutex mutex;  ///< Mutex to protect tasks and agenda.
        std::array<Task, TASKS_SIZE> tasks{};  ///< Holds all tasks to be scheduled.
        miosix::ConditionVariable condvar;     ///< Used when agenda is empty.
        std::priority_queue<Event> agenda;     ///< Ordered list of functions.
    
        PrintLogger logger = Logging::getLogger("taskscheduler");
    };
    
    }  // namespace Boardcore