Skip to content

Commit

Permalink
feat(task): Allow task thread to call stop(); add support for getting…
Browse files Browse the repository at this point in the history
… task thread id (#309)

* feat(task): Allow task thread to call stop(); add support for getting task thread id

* update to ensure task_handle_ is unset when joined

* fix printing of id on other platforms

* update how id is managed for cleaner / more maintainable code

* only use task_handle_ on esp
  • Loading branch information
finger563 authored Aug 15, 2024
1 parent dffe2ca commit cdaddfa
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 11 deletions.
46 changes: 44 additions & 2 deletions components/task/example/main/task_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,9 @@ extern "C" void app_main(void) {
task.start();
auto stop_fn = [&task]() {
std::this_thread::sleep_for(1s);
auto thread = xTaskGetCurrentTaskHandle();
fmt::print("Stopping task from thread {}...\n", fmt::ptr(thread));
// NOTE: on ESP-IDF, this is the same as xTaskGetCurrentTaskHandle();
auto thread = espp::Task::get_current_id();
fmt::print("Stopping task from thread {}...\n", thread);
task.stop();
};
// make vector of threads to stop the task
Expand All @@ -387,6 +388,47 @@ extern "C" void app_main(void) {
//! [Task Request Stop From Multiple Threads example]
}

/**
* Show an example of a task which calls stop on itself from within the task
*/
test_start = std::chrono::high_resolution_clock::now();
{
fmt::print("Task Request Stop From Within Task example\n");
//! [Task Request Stop From Within Task example]
espp::Task task = espp::Task({.name = "Self Stopping Task",
.callback =
[&num_seconds_to_run, &task](std::mutex &m, std::condition_variable &cv) {
static auto begin = std::chrono::high_resolution_clock::now();
auto now = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration<float>(now - begin).count();
fmt::print("Task has run for {:.03f} seconds\n", elapsed);
// NOTE: sleeping in this way allows the sleep to exit early when the
// task is being stopped / destroyed
{
std::unique_lock<std::mutex> lk(m);
cv.wait_for(lk, 100ms);
}
if (elapsed > num_seconds_to_run) {
fmt::print("Stopping task from within task...\n");
task.stop();
}
// do some other work here which can't be preempted, this helps force the
// stopping threads to try to contend on the thread join within the stop
// call
std::this_thread::sleep_for(50ms);
// we don't want to stop yet, so return false
return false;
},

.log_level = espp::Logger::Verbosity::DEBUG});
task.start();
while (task.is_started()) {
std::this_thread::sleep_for(50ms);
}
fmt::print("Task successfully stopped by itself!\n");
//! [Task Request Stop From Within Task example]
}

{
//! [run on core example]
fmt::print("Example main running on core {}\n", xPortGetCoreID());
Expand Down
41 changes: 38 additions & 3 deletions components/task/include/task.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ namespace espp {
*/
class Task : public espp::BaseComponent {
public:
#if defined(ESP_PLATFORM)
typedef void* task_id_t;
#else
typedef std::thread::id task_id_t;
#endif

/**
* @brief Task callback function signature.
*
Expand Down Expand Up @@ -191,10 +197,12 @@ class Task : public espp::BaseComponent {
bool start();

/**
* @brief Stop the task execution, blocking until it stops.
*
* @brief Stop the task execution.
* @details This will request the task to stop, notify the condition variable,
* and (if this calling context is not the task context) join the
* thread.
* @return true if the task stopped, false if it was not started / already
* stopped.
* stopped.
*/
bool stop();

Expand Down Expand Up @@ -231,6 +239,30 @@ class Task : public espp::BaseComponent {
static std::string get_info(const Task &task);
#endif

/**
* @brief Get the ID for this Task's thread / task context.
* @return ID for this Task's thread / task context.
*/
task_id_t get_id() const {
#if defined(ESP_PLATFORM)
return task_handle_;
#else
return thread_.get_id();
#endif
}

/**
* @brief Get the ID for the current thread / task context.
* @return ID for the current thread / task context.
*/
static task_id_t get_current_id() {
#if defined(ESP_PLATFORM)
return static_cast<task_id_t>(xTaskGetCurrentTaskHandle());
#else
return std::this_thread::get_id();
#endif
}

protected:
/**
* @brief Function that is run in the task thread.
Expand All @@ -256,6 +288,9 @@ class Task : public espp::BaseComponent {
std::mutex cv_m_;
std::mutex thread_mutex_;
std::thread thread_;
#if defined(ESP_PLATFORM)
task_id_t task_handle_;
#endif
};
} // namespace espp

Expand Down
20 changes: 14 additions & 6 deletions components/task/src/task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,16 @@ void Task::notify_and_join() {
std::lock_guard<std::mutex> lock(cv_m_);
cv_.notify_all();
}
{
std::lock_guard<std::mutex> lock(thread_mutex_);
if (thread_.joinable()) {
thread_.join();
}
auto thread_id = get_id();
auto current_id = get_current_id();
logger_.debug("Thread id: {}, current id: {}", thread_id, current_id);
// check to ensure we're not the same thread
std::lock_guard<std::mutex> lock(thread_mutex_);
if (thread_.joinable() && current_id != thread_id) {
thread_.join();
#if defined(ESP_PLATFORM)
task_handle_ = nullptr;
#endif
}
}

Expand All @@ -127,14 +132,17 @@ std::string Task::get_info() {
}

std::string Task::get_info(const Task &task) {
TaskHandle_t freertos_handle = xTaskGetHandle(task.name_.c_str());
TaskHandle_t freertos_handle = static_cast<TaskHandle_t>(task.get_id());
return fmt::format("[T] '{}',{},{},{}\n", pcTaskGetName(freertos_handle), xPortGetCoreID(),
uxTaskPriorityGet(freertos_handle),
uxTaskGetStackHighWaterMark(freertos_handle));
}
#endif

void Task::thread_function() {
#if defined(ESP_PLATFORM)
task_handle_ = get_current_id();
#endif
while (started_) {
if (callback_) {
bool should_stop = callback_(cv_m_, cv_);
Expand Down

0 comments on commit cdaddfa

Please sign in to comment.