ModuleBase add common base type and cleanup

This commit is contained in:
Daniel Agar
2020-11-10 14:03:58 -05:00
parent d33a48eb24
commit 700961daf2
96 changed files with 576 additions and 428 deletions
@@ -1,6 +1,6 @@
/****************************************************************************
*
* Copyright (c) 2017 PX4 Development Team. All rights reserved.
* Copyright (c) 2017-2020 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -44,13 +44,23 @@
#include <px4_platform_common/atomic.h>
#include <px4_platform_common/time.h>
#include <px4_platform_common/log.h>
#include <px4_platform_common/posix.h>
#include <px4_platform_common/tasks.h>
#include <systemlib/px4_macros.h>
#ifdef __cplusplus
#include <containers/List.hpp>
#include <containers/LockGuard.hpp>
#include <cstring>
/** @var task_id_is_work_queue Value to indicate if the task runs on the work queue. */
static constexpr const int task_id_is_work_queue = -2;
class ModuleBaseInterface;
extern List<ModuleBaseInterface *> px4_modules_list;
/**
* @brief This mutex protects against race conditions during startup & shutdown of modules.
* There could be one mutex per module instantiation, but to reduce the memory footprint
@@ -59,6 +69,123 @@
*/
extern pthread_mutex_t px4_modules_mutex;
class ModuleBaseInterface : public ListNode<ModuleBaseInterface *>
{
public:
ModuleBaseInterface(const char *name) :
_module_name(name)
{
px4_modules_list.add(this);
}
virtual ~ModuleBaseInterface()
{
px4_modules_list.remove(this);
}
const char *get_name() const { return _module_name; }
/**
* @brief Print the status if the module is running. This can be overridden by the module to provide
* more specific information.
* @return Returns 0 iff successful, -1 otherwise.
*/
virtual int print_status()
{
PX4_INFO_RAW("[%s] running", _module_name);
return 0;
}
/**
* @brief Tells the module to stop (used from outside or inside the module thread).
*/
virtual void request_stop() { _task_should_exit.store(true); }
int task_id() const { return _task_id.load(); }
void set_task_id(int id) { _task_id.store(id); }
/**
* @brief Main loop method for modules running in their own thread. Called from run_trampoline().
* This method must return when should_exit() returns true.
*/
virtual void run() {};
bool running() { return (task_id() != -1); }
/**
* @brief Checks if the module should stop (used within the module thread).
* @return Returns True iff successful, false otherwise.
*/
bool should_exit() const { return _task_should_exit.load(); }
protected:
/**
* @brief lock_module Mutex to lock the module thread.
*/
static void lock_module() { pthread_mutex_lock(&px4_modules_mutex); }
/**
* @brief unlock_module Mutex to unlock the module thread.
*/
static void unlock_module() { pthread_mutex_unlock(&px4_modules_mutex); }
const char *_module_name;
/** @var _task_should_exit Boolean flag to indicate if the task should exit. */
px4::atomic_bool _task_should_exit{false};
/** @var _task_id The task handle: -1 = invalid, otherwise task is assumed to be running. */
px4::atomic_int _task_id{-1};
};
/**
* @brief Get module address by name.
* Caller responsible for locking.
* @param name The module name.
* @return Returns address if found, nullptr otherwise.
*/
ModuleBaseInterface *moduleInstance(const char *name);
/**
* @brief Check if module is running (by name).
* Caller responsible for locking.
* @param name The module name.
* @return Returns true if running, false otherwise.
*/
bool moduleRunning(const char *name);
/**
* @brief Request module stop by name.
* @param name The module name.
* @return Returns 0 on success, -1 otherwise.
*/
int moduleStop(const char *name);
/**
* @brief Check if module is running (by name).
* @param name The module name.
* @return Returns 0 on success, -1 otherwise.
*/
int moduleStatus(const char *name);
/**
* @brief Delete module by name.
* @param name The module name.
*/
void moduleExitAndCleanup(const char *name);
/**
* @brief Wait until module is running (by name).
* @param name The module name.
* @return Returns true if running, false otherwise.
*/
bool moduleWaitUntilRunning(const char *name);
void modulesStatusAll();
void modulesStopAll();
/**
* @class ModuleBase
* Base class for modules, implementing common functionality,
@@ -75,9 +202,8 @@ extern pthread_mutex_t px4_modules_mutex;
* static int task_spawn(int argc, char *argv[])
* {
* // call px4_task_spawn_cmd() with &run_trampoline as startup method
* // optional: wait until _object is not null, which means the task got initialized (use a timeout)
* // set _task_id and return 0
* // on error return != 0 (and _task_id must be -1)
* // optional: wait until object is not null, which means the task got initialized (use a timeout)
* // on error return != 0
* }
*
* static T *instantiate(int argc, char *argv[])
@@ -104,19 +230,24 @@ extern pthread_mutex_t px4_modules_mutex;
* - custom_command & print_usage as above
* static int task_spawn(int argc, char *argv[]) {
* // parse the arguments
* // set _object (here or from the work_queue() callback)
* // set object (here or from the work_queue() callback)
* // call work_queue() (with a custom cycle trampoline)
* // optional: wait until _object is not null, which means the task got initialized (use a timeout)
* // set _task_id to task_id_is_work_queue and return 0
* // on error return != 0 (and _task_id must be -1)
* // instance->set_task_id(task_id_is_work_queue); and return 0
* // on error return != 0
* }
*/
template<class T>
class ModuleBase
class ModuleBase : public ModuleBaseInterface
{
public:
ModuleBase() : _task_should_exit{false} {}
virtual ~ModuleBase() {}
ModuleBase() : ModuleBaseInterface(get_name_static())
{}
virtual ~ModuleBase() = default;
#if defined(MODULE_NAME)
static constexpr const char *get_name_static() { return MODULE_NAME; }
#endif // MODULE_NAME
/**
* @brief main Main entry point to the module that should be
@@ -132,6 +263,7 @@ public:
strcmp(argv[1], "help") == 0 ||
strcmp(argv[1], "info") == 0 ||
strcmp(argv[1], "usage") == 0) {
return T::print_usage();
}
@@ -141,18 +273,14 @@ public:
}
if (strcmp(argv[1], "status") == 0) {
return status_command();
return moduleStatus(get_name_static());
}
if (strcmp(argv[1], "stop") == 0) {
return stop_command();
return moduleStop(get_name_static());
}
lock_module(); // Lock here, as the method could access _object.
int ret = T::custom_command(argc - 1, argv + 1);
unlock_module();
return ret;
return T::custom_command(argc - 1, argv + 1);
}
/**
@@ -175,15 +303,19 @@ public:
argv += 1;
#endif
// lock module, instantiate will create module object and add to list
lock_module();
T *object = T::instantiate(argc, argv);
_object.store(object);
if (object) {
object->set_task_id(px4_getpid());
unlock_module();
object->run();
} else {
PX4_ERR("failed to instantiate object");
ret = -1;
unlock_module();
}
exit_and_cleanup();
@@ -201,9 +333,9 @@ public:
static int start_command_base(int argc, char *argv[])
{
int ret = 0;
lock_module();
LockGuard lg(px4_modules_mutex);
if (is_running()) {
if (moduleRunning(get_name_static())) {
ret = -1;
PX4_ERR("Task already running");
@@ -215,102 +347,14 @@ public:
}
}
unlock_module();
return ret;
}
/**
* @brief Stops the command, ('command stop'), checks if it is running and if it is, request the module to stop
* and waits for the task to complete.
* @return Returns 0 iff successful, -1 otherwise.
*/
static int stop_command()
{
int ret = 0;
lock_module();
if (is_running()) {
T *object = _object.load();
if (object) {
object->request_stop();
unsigned int i = 0;
do {
unlock_module();
px4_usleep(10000); // 10 ms
lock_module();
if (++i > 500 && _task_id != -1) { // wait at most 5 sec
PX4_ERR("timeout, forcing stop");
if (_task_id != task_id_is_work_queue) {
px4_task_delete(_task_id);
}
_task_id = -1;
delete _object.load();
_object.store(nullptr);
ret = -1;
break;
}
} while (_task_id != -1);
} else {
// In the very unlikely event that can only happen on work queues,
// if the starting thread does not wait for the work queue to initialize,
// and inside the work queue, the allocation of _object fails
// and exit_and_cleanup() is not called, set the _task_id as invalid.
_task_id = -1;
}
}
unlock_module();
return ret;
}
/**
* @brief Handle 'command status': check if running and call print_status() if it is
* @return Returns 0 iff successful, -1 otherwise.
*/
static int status_command()
{
int ret = -1;
lock_module();
if (is_running() && _object.load()) {
T *object = _object.load();
ret = object->print_status();
} else {
PX4_INFO("not running");
}
unlock_module();
return ret;
}
/**
* @brief Print the status if the module is running. This can be overridden by the module to provide
* more specific information.
* @return Returns 0 iff successful, -1 otherwise.
*/
virtual int print_status()
{
PX4_INFO("running");
return 0;
}
/**
* @brief Main loop method for modules running in their own thread. Called from run_trampoline().
* This method must return when should_exit() returns true.
*/
virtual void run()
{
}
virtual void run() {}
/**
* @brief Returns the status of the module.
@@ -318,118 +362,36 @@ public:
*/
static bool is_running()
{
return _task_id != -1;
LockGuard lg(px4_modules_mutex);
return moduleRunning(get_name_static());
}
protected:
/**
* @brief Tells the module to stop (used from outside or inside the module thread).
*/
virtual void request_stop()
{
_task_should_exit.store(true);
}
/**
* @brief Checks if the module should stop (used within the module thread).
* @return Returns True iff successful, false otherwise.
*/
bool should_exit() const
{
return _task_should_exit.load();
}
/**
* @brief Exits the module and delete the object. Called from within the module's thread.
* For work queue modules, this needs to be called from the derived class in the
* cycle method, when should_exit() returns true.
*/
static void exit_and_cleanup()
{
// Take the lock here:
// - if startup fails and we're faster than the parent thread, it will set
// _task_id and subsequently it will look like the task is running.
// - deleting the object must take place inside the lock.
lock_module();
delete _object.load();
_object.store(nullptr);
_task_id = -1; // Signal a potentially waiting thread for the module to exit that it can continue.
unlock_module();
}
static void exit_and_cleanup() { moduleExitAndCleanup(get_name_static()); }
/**
* @brief Waits until _object is initialized, (from the new thread). This can be called from task_spawn().
* @return Returns 0 iff successful, -1 on timeout or otherwise.
*/
static int wait_until_running()
{
int i = 0;
do {
/* Wait up to 1s. */
px4_usleep(2500);
} while (!_object.load() && ++i < 400);
if (i == 400) {
PX4_ERR("Timed out while waiting for thread to start");
return -1;
}
return 0;
}
static int wait_until_running() { return moduleWaitUntilRunning(get_name_static()); }
/**
* @brief Get the module's object instance, (this is null if it's not running).
*/
static T *get_instance()
{
return (T *)_object.load();
LockGuard lg(px4_modules_mutex);
return (T *)moduleInstance(get_name_static());
}
/**
* @var _object Instance if the module is running.
* @note There will be one instance for each template type.
*/
static px4::atomic<T *> _object;
/** @var _task_id The task handle: -1 = invalid, otherwise task is assumed to be running. */
static int _task_id;
/** @var task_id_is_work_queue Value to indicate if the task runs on the work queue. */
static constexpr const int task_id_is_work_queue = -2;
private:
/**
* @brief lock_module Mutex to lock the module thread.
*/
static void lock_module()
{
pthread_mutex_lock(&px4_modules_mutex);
}
/**
* @brief unlock_module Mutex to unlock the module thread.
*/
static void unlock_module()
{
pthread_mutex_unlock(&px4_modules_mutex);
}
/** @var _task_should_exit Boolean flag to indicate if the task should exit. */
px4::atomic_bool _task_should_exit{false};
};
template<class T>
px4::atomic<T *> ModuleBase<T>::_object{nullptr};
template<class T>
int ModuleBase<T>::_task_id = -1;
#endif /* __cplusplus */