Skip to content
Snippets Groups Projects
Commit a25e653f authored by Guillaume Roguez's avatar Guillaume Roguez Committed by gerrit2
Browse files

threadloop: fix deadlock issue

start() can deadlock into join if a previous stop has been called and
the thread hasn't seen it before started again.

This patch solves that by using a transactional stop (using FSM).
This also adds MoveConstructible and MoveAssignement behaviour.

Issue: #80556
Change-Id: Ie04e3a20007e69b4d372b9619d67710f4e2af1b7
parent 8d9028f6
No related branches found
No related tags found
No related merge requests found
...@@ -34,13 +34,16 @@ ...@@ -34,13 +34,16 @@
namespace ring { namespace ring {
void ThreadLoop::mainloop() void
ThreadLoop::mainloop(const std::function<bool()> setup,
const std::function<void()> process,
const std::function<void()> cleanup)
{ {
try { try {
if (setup_()) { if (setup()) {
while (running_) while (state_ == RUNNING)
process_(); process();
cleanup_(); cleanup();
} else { } else {
RING_ERR("setup failed"); RING_ERR("setup failed");
} }
...@@ -52,9 +55,22 @@ void ThreadLoop::mainloop() ...@@ -52,9 +55,22 @@ void ThreadLoop::mainloop()
ThreadLoop::ThreadLoop(const std::function<bool()>& setup, ThreadLoop::ThreadLoop(const std::function<bool()>& setup,
const std::function<void()>& process, const std::function<void()>& process,
const std::function<void()>& cleanup) const std::function<void()>& cleanup)
: setup_(setup), process_(process), cleanup_(cleanup) : setup_(setup)
, process_(process)
, cleanup_(cleanup)
, thread_()
{} {}
ThreadLoop::ThreadLoop(ThreadLoop&& other)
: setup_(std::move(other.setup_))
, process_(std::move(other.process_))
, cleanup_(std::move(other.cleanup_))
, state_(other.state_.load())
, thread_(std::move(other.thread_))
{
other.state_ = READY;
}
ThreadLoop::~ThreadLoop() ThreadLoop::~ThreadLoop()
{ {
if (isRunning()) { if (isRunning()) {
...@@ -63,24 +79,35 @@ ThreadLoop::~ThreadLoop() ...@@ -63,24 +79,35 @@ ThreadLoop::~ThreadLoop()
} }
} }
void ThreadLoop::start() void
ThreadLoop::start()
{ {
if (!running_.exchange(true)) { const auto s = state_.load();
// a previous stop() call may be pending
if (thread_.joinable()) if (s == RUNNING) {
RING_ERR("already started");
return;
}
// stop pending but not processed by thread yet?
if (s == STOPPING and thread_.joinable()) {
RING_DBG("stop pending");
thread_.join(); thread_.join();
thread_ = std::thread(&ThreadLoop::mainloop, this);
} else {
RING_ERR("Thread already started");
} }
state_ = RUNNING;
thread_ = std::thread(&ThreadLoop::mainloop, this, setup_, process_, cleanup_);
} }
void ThreadLoop::stop() void
ThreadLoop::stop()
{ {
running_ = false; if (state_ == RUNNING)
state_ = STOPPING;
} }
void ThreadLoop::join() void
ThreadLoop::join()
{ {
stop(); stop();
if (thread_.joinable()) if (thread_.joinable())
...@@ -93,9 +120,10 @@ void ThreadLoop::exit() ...@@ -93,9 +120,10 @@ void ThreadLoop::exit()
throw ThreadLoopException(); throw ThreadLoopException();
} }
bool ThreadLoop::isRunning() const bool
ThreadLoop::isRunning() const noexcept
{ {
return running_; return thread_.joinable() and state_ == RUNNING;
} }
} // namespace ring } // namespace ring
...@@ -29,8 +29,7 @@ ...@@ -29,8 +29,7 @@
* as that of the covered work. * as that of the covered work.
*/ */
#ifndef __THREADLOOP_H__ #pragma once
#define __THREADLOOP_H__
#include <atomic> #include <atomic>
#include <thread> #include <thread>
...@@ -50,17 +49,22 @@ struct ThreadLoopException : public std::runtime_error { ...@@ -50,17 +49,22 @@ struct ThreadLoopException : public std::runtime_error {
class ThreadLoop { class ThreadLoop {
public: public:
enum ThreadState {READY, RUNNING, STOPPING};
ThreadLoop(const std::function<bool()>& setup, ThreadLoop(const std::function<bool()>& setup,
const std::function<void()>& process, const std::function<void()>& process,
const std::function<void()>& cleanup); const std::function<void()>& cleanup);
ThreadLoop(ThreadLoop&&);
~ThreadLoop(); ~ThreadLoop();
void start(); void start();
void exit(); void exit();
void stop(); void stop();
void join(); void join();
bool isRunning() const;
bool isRunning() const noexcept;
private: private:
// These must be provided by users of ThreadLoop // These must be provided by users of ThreadLoop
...@@ -68,12 +72,12 @@ private: ...@@ -68,12 +72,12 @@ private:
std::function<void()> process_; std::function<void()> process_;
std::function<void()> cleanup_; std::function<void()> cleanup_;
void mainloop(); void mainloop(const std::function<bool()> setup,
const std::function<void()> process,
const std::function<void()> cleanup);
std::atomic<bool> running_ = {false}; std::atomic<ThreadState> state_ {READY};
std::thread thread_ = {}; std::thread thread_;
}; };
} // namespace ring } // namespace ring
#endif // __THREADLOOP_H__
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment