From 37ac1b66ddbf0e26992bc4a70e8e98efc969f80d Mon Sep 17 00:00:00 2001 From: WerWolv Date: Wed, 6 Dec 2023 11:04:35 +0100 Subject: [PATCH] refactor: Task Manager related code --- lib/libimhex/include/hex/api/task_manager.hpp | 2 + lib/libimhex/source/api/task_manager.cpp | 110 ++++++++++++------ main/gui/source/init/tasks.cpp | 6 +- main/gui/source/main.cpp | 4 +- plugins/builtin/source/content/ui_items.cpp | 9 +- 5 files changed, 85 insertions(+), 46 deletions(-) diff --git a/lib/libimhex/include/hex/api/task_manager.hpp b/lib/libimhex/include/hex/api/task_manager.hpp index 49e9ce402..888b82f74 100644 --- a/lib/libimhex/include/hex/api/task_manager.hpp +++ b/lib/libimhex/include/hex/api/task_manager.hpp @@ -166,6 +166,8 @@ namespace hex { private: static void runner(const std::stop_token &stopToken); + + static TaskHolder createTask(std::string name, u64 maxValue, bool background, std::function function); }; } \ No newline at end of file diff --git a/lib/libimhex/source/api/task_manager.cpp b/lib/libimhex/source/api/task_manager.cpp index 731e6797a..1327982b2 100644 --- a/lib/libimhex/source/api/task_manager.cpp +++ b/lib/libimhex/source/api/task_manager.cpp @@ -82,8 +82,11 @@ namespace hex { } void Task::update(u64 value) { + // Update the current progress value of the task this->m_currValue.store(value, std::memory_order_relaxed); + // Check if the task has been interrupted by the main thread and if yes, + // throw an exception that is generally not caught by the task if (this->m_shouldInterrupt.load(std::memory_order_relaxed)) [[unlikely]] throw TaskInterruptor(); } @@ -96,6 +99,7 @@ namespace hex { void Task::interrupt() { this->m_shouldInterrupt = true; + // Call the interrupt callback on the current thread if one is set if (this->m_interruptCallback) this->m_interruptCallback(); } @@ -157,59 +161,62 @@ namespace hex { void Task::exception(const char *message) { std::scoped_lock lock(this->m_mutex); + // Store information about the caught exception this->m_exceptionMessage = message; this->m_hadException = true; } bool TaskHolder::isRunning() const { - if (this->m_task.expired()) + auto task = this->m_task.lock(); + if (!task) return false; - auto task = this->m_task.lock(); return !task->isFinished(); } bool TaskHolder::hadException() const { - if (this->m_task.expired()) + auto task = this->m_task.lock(); + if (!task) return false; - auto task = this->m_task.lock(); return !task->hadException(); } bool TaskHolder::shouldInterrupt() const { - if (this->m_task.expired()) + auto task = this->m_task.lock(); + if (!task) return false; - auto task = this->m_task.lock(); return !task->shouldInterrupt(); } bool TaskHolder::wasInterrupted() const { - if (this->m_task.expired()) + auto task = this->m_task.lock(); + if (!task) return false; - auto task = this->m_task.lock(); return !task->wasInterrupted(); } void TaskHolder::interrupt() const { - if (this->m_task.expired()) + auto task = this->m_task.lock(); + if (!task) return; - auto task = this->m_task.lock(); task->interrupt(); } u32 TaskHolder::getProgress() const { - if (this->m_task.expired()) - return 0; - auto task = this->m_task.lock(); + if (!task) + return false; + + // If the max value is 0, the task has no progress if (task->getMaxValue() == 0) return 0; + // Calculate the progress of the task from 0 to 100 return u32((task->getValue() * 100) / task->getMaxValue()); } @@ -219,49 +226,74 @@ namespace hex { log::debug("Initializing task manager thread pool with {} workers.", threadCount); + // Create worker threads for (u32 i = 0; i < threadCount; i++) s_workers.emplace_back(TaskManager::runner); } void TaskManager::exit() { - for (auto &task : s_tasks) + // Interrupt all tasks + for (auto &task : s_tasks) { task->interrupt(); + } + // Ask worker threads to exit after finishing their task for (auto &thread : s_workers) thread.request_stop(); + // Wake up all the idle worker threads so they can exit s_jobCondVar.notify_all(); + // Wait for all worker threads to exit s_workers.clear(); + + s_tasks.clear(); + s_taskQueue.clear(); } void TaskManager::runner(const std::stop_token &stopToken) { while (true) { std::shared_ptr task; + + // Set the thread name to "Idle Task" while waiting for a task + setThreadName("Idle Task"); + { + // Wait for a task to be added to the queue std::unique_lock lock(s_queueMutex); s_jobCondVar.wait(lock, [&] { return !s_taskQueue.empty() || stopToken.stop_requested(); }); + + // Check if the thread should exit if (stopToken.stop_requested()) break; + // Grab the next task from the queue task = std::move(s_taskQueue.front()); s_taskQueue.pop_front(); } try { + // Set the thread name to the name of the task setThreadName(Lang(task->m_unlocalizedName)); + + // Execute the task task->m_function(*task); - setThreadName("Idle Task"); - log::debug("Finished task {}", task->m_unlocalizedName); + + log::debug("Task '{}' finished", task->m_unlocalizedName); } catch (const Task::TaskInterruptor &) { + // Handle the task being interrupted by user request task->interruption(); } catch (const std::exception &e) { - log::error("Exception in task {}: {}", task->m_unlocalizedName, e.what()); + log::error("Exception in task '{}': {}", task->m_unlocalizedName, e.what()); + + // Handle the task throwing an uncaught exception task->exception(e.what()); } catch (...) { - log::error("Exception in task {}", task->m_unlocalizedName); + log::error("Exception in task '{}'", task->m_unlocalizedName); + + // Handle the task throwing an uncaught exception of unknown type task->exception("Unknown Exception"); } @@ -269,39 +301,43 @@ namespace hex { } } - TaskHolder TaskManager::createTask(std::string name, u64 maxValue, std::function function) { - log::debug("Creating task {}", name); - std::unique_lock lock(s_queueMutex); - auto task = std::make_shared(std::move(name), maxValue, false, std::move(function)); + TaskHolder TaskManager::createTask(std::string name, u64 maxValue, bool background, std::function function) { + std::scoped_lock lock(s_queueMutex); + + // Construct new task + auto task = std::make_shared(std::move(name), maxValue, background, std::move(function)); + s_tasks.emplace_back(task); - s_taskQueue.emplace_back(task); + + // Add task to the queue for the worker to pick up + s_taskQueue.emplace_back(std::move(task)); s_jobCondVar.notify_one(); return TaskHolder(s_tasks.back()); } + + TaskHolder TaskManager::createTask(std::string name, u64 maxValue, std::function function) { + log::debug("Creating task {}", name); + return createTask(std::move(name), maxValue, false, std::move(function)); + } + TaskHolder TaskManager::createBackgroundTask(std::string name, std::function function) { log::debug("Creating background task {}", name); - std::unique_lock lock(s_queueMutex); - - auto task = std::make_shared(std::move(name), 0, true, std::move(function)); - s_tasks.emplace_back(task); - s_taskQueue.emplace_back(task); - - s_jobCondVar.notify_one(); - - return TaskHolder(s_tasks.back()); + return createTask(std::move(name), 0, true, std::move(function)); } void TaskManager::collectGarbage() { { - std::unique_lock lock1(s_queueMutex); - std::erase_if(s_tasks, [](const auto &task) { return task->isFinished() && !task->hadException(); }); + std::scoped_lock lock(s_queueMutex); + std::erase_if(s_tasks, [](const auto &task) { + return task->isFinished() && !task->hadException(); + }); } if (s_tasks.empty()) { - std::unique_lock lock2(s_deferredCallsMutex); + std::scoped_lock lock(s_deferredCallsMutex); for (auto &call : s_tasksFinishedCallbacks) call(); s_tasksFinishedCallbacks.clear(); @@ -314,7 +350,7 @@ namespace hex { } size_t TaskManager::getRunningTaskCount() { - std::unique_lock lock(s_queueMutex); + std::scoped_lock lock(s_queueMutex); return std::count_if(s_tasks.begin(), s_tasks.end(), [](const auto &task){ return !task->isBackgroundTask(); @@ -322,7 +358,7 @@ namespace hex { } size_t TaskManager::getRunningBackgroundTaskCount() { - std::unique_lock lock(s_queueMutex); + std::scoped_lock lock(s_queueMutex); return std::count_if(s_tasks.begin(), s_tasks.end(), [](const auto &task){ return task->isBackgroundTask(); diff --git a/main/gui/source/init/tasks.cpp b/main/gui/source/init/tasks.cpp index 14cf23393..66872809e 100644 --- a/main/gui/source/init/tasks.cpp +++ b/main/gui/source/init/tasks.cpp @@ -63,9 +63,11 @@ namespace hex::init { // destructors to be called correctly. To prevent crashes when ImHex exits, we need to delete all shared data EventManager::post(); - EventManager::clear(); + // Terminate all asynchronous tasks + TaskManager::exit(); + while (ImHexApi::Provider::isValid()) ImHexApi::Provider::remove(ImHexApi::Provider::get()); ContentRegistry::Provider::impl::getEntries().clear(); @@ -114,8 +116,6 @@ namespace hex::init { ShortcutManager::clearShortcuts(); - TaskManager::getRunningTasks().clear(); - ContentRegistry::DataProcessorNode::impl::getEntries().clear(); ContentRegistry::DataFormatter::impl::getEntries().clear(); diff --git a/main/gui/source/main.cpp b/main/gui/source/main.cpp index 3b3fd638c..1e204df27 100644 --- a/main/gui/source/main.cpp +++ b/main/gui/source/main.cpp @@ -116,14 +116,12 @@ namespace { /** - * @brief Deinitializes ImHex by running all exit tasks and terminating all asynchronous tasks + * @brief Deinitializes ImHex by running all exit tasks */ void deinitializeImHex() { // Run exit tasks init::runExitTasks(); - // Terminate all asynchronous tasks - TaskManager::exit(); } /** diff --git a/plugins/builtin/source/content/ui_items.cpp b/plugins/builtin/source/content/ui_items.cpp index f4a66b1b7..e4e2769f1 100644 --- a/plugins/builtin/source/content/ui_items.cpp +++ b/plugins/builtin/source/content/ui_items.cpp @@ -84,9 +84,12 @@ namespace hex::plugin::builtin { const auto &tasks = TaskManager::getRunningTasks(); const auto &frontTask = tasks.front(); - const auto progress = frontTask->getMaxValue() == 0 ? 1 : float(frontTask->getValue()) / frontTask->getMaxValue(); + if (frontTask == nullptr) + return; - ImHexApi::System::setTaskBarProgress(ImHexApi::System::TaskProgressState::Progress, ImHexApi::System::TaskProgressType::Normal, progress * 100); + const auto progress = frontTask->getMaxValue() == 0 ? 1 : float(frontTask->getValue()) / float(frontTask->getMaxValue()); + + ImHexApi::System::setTaskBarProgress(ImHexApi::System::TaskProgressState::Progress, ImHexApi::System::TaskProgressType::Normal, u32(progress * 100)); const auto widgetStart = ImGui::GetCursorPos(); { @@ -113,7 +116,7 @@ namespace hex::plugin::builtin { ImGui::SameLine(); ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); ImGui::SameLine(); - ImGuiExt::SmallProgressBar(frontTask->getMaxValue() == 0 ? 1 : (float(frontTask->getValue()) / frontTask->getMaxValue()), (ImGui::GetTextLineHeightWithSpacing() - 5_scaled) / 2); + ImGuiExt::SmallProgressBar(task->getMaxValue() == 0 ? 1 : (float(task->getValue()) / float(task->getMaxValue())), (ImGui::GetTextLineHeightWithSpacing() - 5_scaled) / 2); ImGui::SameLine(); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));