LCOV - code coverage report
Current view: top level - src/esys/repo/manifest - runtasks_manifest.cpp (source / functions) Hit Total Coverage
Test: esysrepo_coverage.info Lines: 137 164 83.5 %
Date: 2026-08-01 10:43:40 Functions: 15 23 65.2 %

          Line data    Source code
       1             : /*!
       2             :  * \file esys/repo/manifest/runtasks_manifest.cpp
       3             :  * \brief
       4             :  *
       5             :  * \cond
       6             :  * __legal_b__
       7             :  *
       8             :  * Copyright (c) 2020-2023 Michel Gillet
       9             :  * Distributed under the MIT License.
      10             :  * (See accompanying file LICENSE.txt or
      11             :  * copy at https://opensource.org/licenses/MIT)
      12             :  *
      13             :  * __legal_e__
      14             :  * \endcond
      15             :  *
      16             :  */
      17             : 
      18             : #include "esys/repo/esysrepo_prec.h"
      19             : #include "esys/repo/manifest/runtasks.h"
      20             : #include "esys/repo/progress/repoassignedevent.h"
      21             : #include "esys/repo/progress/repodoneevent.h"
      22             : #include "esys/repo/progress/repofailedevent.h"
      23             : #include "esys/repo/progress/schedulerstateevent.h"
      24             : 
      25             : #include <esys/log/consolelockguard.h>
      26             : 
      27             : #include <esys/trace/call.h>
      28             : #include <esys/trace/macros.h>
      29             : 
      30             : #include <chrono>
      31             : #include <sstream>
      32             : #include <iostream>
      33             : 
      34             : namespace esys::repo::manifest
      35             : {
      36             : 
      37          55 : RunTasks::RunTasks()
      38         275 :     : log::User()
      39             : {
      40          55 : }
      41             : 
      42          55 : RunTasks::~RunTasks()
      43             : {
      44          55 :     m_tasks.clear();
      45         275 : }
      46             : 
      47           2 : void RunTasks::add(std::shared_ptr<TaskBase> task)
      48             : {
      49           4 :     add(task, m_task_id);
      50           2 : }
      51             : 
      52         122 : void RunTasks::add(std::shared_ptr<TaskBase> task, std::size_t id)
      53             : {
      54         122 :     task->set_id(id);
      55         122 :     if (id >= m_task_id) m_task_id = id + 1;
      56         122 :     m_tasks.push_back(task);
      57         122 : }
      58             : 
      59          55 : void RunTasks::set_job_count(std::size_t job_count)
      60             : {
      61          55 :     m_job_count = job_count;
      62          55 : }
      63             : 
      64         186 : std::size_t RunTasks::get_job_count() const
      65             : {
      66         186 :     return m_job_count;
      67             : }
      68             : 
      69           1 : void RunTasks::set_progress_session(progress::ProgressSession *progress_session)
      70             : {
      71           1 :     m_progress_session = progress_session;
      72           1 : }
      73             : 
      74           0 : progress::ProgressSession *RunTasks::get_progress_session() const
      75             : {
      76           0 :     return m_progress_session;
      77             : }
      78             : 
      79           0 : void RunTasks::set_console_progress(bool console_progress)
      80             : {
      81           0 :     m_console_progress = console_progress;
      82           0 : }
      83             : 
      84           0 : bool RunTasks::get_console_progress() const
      85             : {
      86           0 :     return m_console_progress;
      87             : }
      88             : 
      89           0 : void RunTasks::set_paused(bool paused)
      90             : {
      91           0 :     m_paused.store(paused);
      92           0 :     push_scheduler_state();
      93           0 : }
      94             : 
      95           0 : bool RunTasks::get_paused() const
      96             : {
      97           0 :     return m_paused.load();
      98             : }
      99             : 
     100           0 : void RunTasks::set_backpressure(bool backpressure)
     101             : {
     102           0 :     m_backpressure.store(backpressure);
     103           0 :     push_scheduler_state();
     104           0 : }
     105             : 
     106           0 : bool RunTasks::get_backpressure() const
     107             : {
     108           0 :     return m_backpressure.load();
     109             : }
     110             : 
     111         355 : void RunTasks::push_scheduler_state()
     112             : {
     113         355 :     if (m_progress_session == nullptr) return;
     114             : 
     115         710 :     m_progress_session->push(progress::SchedulerStateEvent(
     116         355 :         m_tasks.size(), m_tasks_running.size(), m_worker_threads.size(), m_paused.load(), m_backpressure.load()));
     117             : }
     118             : 
     119         111 : std::size_t RunTasks::get_worker_thread_count()
     120             : {
     121         111 :     m_worker_thread_count = get_job_count();
     122             : 
     123         111 :     if (m_tasks.size() < m_worker_thread_count) m_worker_thread_count = m_tasks.size();
     124             : 
     125         111 :     return m_worker_thread_count;
     126             : }
     127             : 
     128          35 : Result RunTasks::run()
     129             : {
     130          35 :     Result result;
     131          35 :     ETRC_CALL_RET_NP(result);
     132             : 
     133          35 :     if (m_tasks.size() == 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::RUNTASKS_NO_TASK));
     134          35 :     if (static_cast<int>(get_job_count()) < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::JOB_COUNT_NEGATIVE));
     135             : 
     136          35 :     progress::ProgressSession owned_session;
     137          35 :     progress::ConsoleObserver console_observer;
     138          35 :     progress::ProgressSession *const external_session = m_progress_session;
     139          35 :     const bool own_session = (external_session == nullptr);
     140             : 
     141          35 :     if (own_session) m_progress_session = &owned_session;
     142             : 
     143          35 :     const bool console_active = m_console_progress && own_session;
     144          35 :     if (console_active)
     145             :     {
     146          34 :         console_observer.set_enabled(true);
     147          34 :         m_progress_session->add_observer(&console_observer);
     148          34 :         m_console_observer = &console_observer;
     149             :     }
     150             :     else
     151             :     {
     152           1 :         m_console_observer = nullptr;
     153             :     }
     154             : 
     155          35 :     m_done = false;
     156          35 :     m_any_failed = false;
     157             : 
     158         111 :     for (int idx = 0; idx < get_worker_thread_count(); ++idx)
     159             :     {
     160          76 :         std::shared_ptr<WorkerThread> worker = std::make_shared<WorkerThread>(idx);
     161             : 
     162         152 :         m_threads.push_back(std::thread(&RunTasks::worker_thread, this, worker));
     163             : 
     164          76 :         m_worker_threads.push_back(worker);
     165          76 :     }
     166             : 
     167          35 :     push_scheduler_state();
     168             : 
     169          35 :     if (console_active) m_cout_thread = std::thread(&RunTasks::cout_thread, this);
     170             : 
     171          35 :     wait_done();
     172             : 
     173         111 :     for (auto &thread : m_threads)
     174             :     {
     175          76 :         if (thread.joinable()) thread.join();
     176             :     }
     177             : 
     178          35 :     if (m_cout_thread.joinable()) m_cout_thread.join();
     179             : 
     180          35 :     if (console_active) m_progress_session->remove_observer(&console_observer);
     181          35 :     m_console_observer = nullptr;
     182          35 :     if (own_session) m_progress_session = external_session;
     183             : 
     184          35 :     m_threads.clear();
     185          35 :     m_worker_threads.clear();
     186             : 
     187          36 :     if (m_any_failed) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR));
     188             : 
     189          68 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     190          35 : }
     191             : 
     192          76 : void RunTasks::worker_thread(std::shared_ptr<WorkerThread> thread)
     193             : {
     194         228 :     RemoveWorkerThreadGuard remove_guard(this, thread);
     195          76 :     std::shared_ptr<TaskBase> task;
     196             : 
     197          76 :     std::ostringstream oss;
     198          76 :     oss << "Thread " << thread->get_id();
     199         152 :     ETRC_SET_THREAD_NAME(oss.str().c_str());
     200             : 
     201          76 :     int result = m_tasks.pop_front(task);
     202         198 :     while (result == 0)
     203             :     {
     204         122 :         task->set_worker_id(thread->get_id());
     205         122 :         task->set_progress_session(m_progress_session);
     206             : 
     207         122 :         if (m_progress_session != nullptr)
     208         244 :             m_progress_session->push(progress::RepoAssignedEvent(task->get_id(), thread->get_id()));
     209             : 
     210         122 :         m_tasks_running.push_back(task);
     211         122 :         push_scheduler_state();
     212             : 
     213         122 :         try
     214             :         {
     215         122 :             result = task->run();
     216         122 :             if (result < 0) task->set_result(result);
     217             :         }
     218           0 :         catch (const std::exception &e)
     219             :         {
     220           0 :             task->set_result(-1);
     221           0 :             task->add_errors(e.what());
     222           0 :         }
     223           0 :         catch (...)
     224             :         {
     225           0 :             task->set_result(-1);
     226           0 :             task->add_errors("Unknown exception.");
     227           0 :         }
     228         122 :         if ((result < 0) || (task->get_result() < 0))
     229             :         {
     230           1 :             m_any_failed = true;
     231           2 :             auto l_result = ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR);
     232           1 :             std::ostringstream err_oss;
     233           1 :             l_result.print(err_oss);
     234           1 :             error(err_oss.str());
     235           2 :             for (const auto &err : task->get_errors()) error(err);
     236             : 
     237           1 :             if (m_progress_session != nullptr)
     238             :             {
     239           1 :                 std::string fail_msg = "task failed";
     240           1 :                 if (!task->get_errors().empty()) fail_msg = task->get_errors().front();
     241           3 :                 m_progress_session->push(progress::RepoFailedEvent(task->get_id(), fail_msg));
     242           1 :             }
     243           1 :         }
     244         121 :         else if (m_progress_session != nullptr)
     245             :         {
     246         242 :             m_progress_session->push(progress::RepoDoneEvent(task->get_id()));
     247             :         }
     248             : 
     249         122 :         task->set_progress_session(nullptr);
     250         122 :         m_tasks_done.push_back(task);
     251             : 
     252         122 :         std::size_t id = task->get_id();
     253         302 :         auto remove_fct = [id](const std::shared_ptr<TaskBase> &task) { return (task->get_id() == id); };
     254         122 :         m_tasks_running.remove_if(remove_fct);
     255             : 
     256         122 :         push_scheduler_state();
     257             : 
     258         122 :         result = m_tasks.pop_front(task);
     259             :     }
     260          76 : }
     261             : 
     262          34 : void RunTasks::cout_thread()
     263             : {
     264         233 :     while (!m_done)
     265             :     {
     266         199 :         std::this_thread::sleep_for(std::chrono::milliseconds(m_cout_interval_ms));
     267             : 
     268         199 :         if (m_console_observer == nullptr) continue;
     269             : 
     270         199 :         const std::string line = m_console_observer->format_status_line();
     271         199 :         if (line.empty()) continue;
     272         165 :         {
     273         165 :             log::ConsoleLockGuard<log::User> lock(this);
     274             : 
     275             :             // Pad so a shorter line fully clears a previous longer \\r status.
     276         165 :             std::cout << '\r' << line << "                    " << std::flush;
     277         165 :         }
     278         199 :     }
     279             : 
     280             :     // Finish the status line so following summary logs are not overwritten.
     281          34 :     {
     282          34 :         log::ConsoleLockGuard<log::User> lock(this);
     283          34 :         std::cout << '\n' << std::flush;
     284          34 :     }
     285          34 : }
     286             : 
     287          35 : void RunTasks::wait_done()
     288             : {
     289          35 :     std::unique_lock<std::mutex> lock{m_done_mutex};
     290         145 :     m_done_cond.wait(lock, [this] { return (m_done == true); });
     291          35 : }
     292             : 
     293          76 : void RunTasks::remove(std::shared_ptr<WorkerThread> rem_worker_thread)
     294             : {
     295         380 :     auto remove_fct = [rem_worker_thread](const std::shared_ptr<WorkerThread> &worker_thread) {
     296         152 :         return (rem_worker_thread->get_id() == worker_thread->get_id());
     297          76 :     };
     298             : 
     299          76 :     m_worker_threads.remove_if(remove_fct);
     300          76 :     m_worker_thread_count = m_worker_threads.size();
     301          76 :     if (m_worker_thread_count == 0) m_done = true;
     302          76 :     m_done_cond.notify_one();
     303          76 :     push_scheduler_state();
     304          76 : }
     305             : 
     306             : } // namespace esys::repo::manifest

Generated by: LCOV version 1.14