blob: 7e18e1b8e79fb0e934237e0150ad04cac895801e [file] [log] [blame]
Adrien Béraud612b55b2023-05-29 10:42:04 -04001/*
2 * Copyright (C) 2004-2023 Savoir-faire Linux Inc.
3 *
Adrien Béraudcb753622023-07-17 22:32:49 -04004 * This program is free software: you can redistribute it and/or modify
Adrien Béraud612b55b2023-05-29 10:42:04 -04005 * it under the terms of the GNU General Public License as published by
Adrien Béraudcb753622023-07-17 22:32:49 -04006 * the Free Software Foundation, either version 3 of the License, or
Adrien Béraud612b55b2023-05-29 10:42:04 -04007 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Adrien Béraudcb753622023-07-17 22:32:49 -040011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Adrien Béraud612b55b2023-05-29 10:42:04 -040012 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
Adrien Béraudcb753622023-07-17 22:32:49 -040015 * along with this program. If not, see <https://www.gnu.org/licenses/>.
Adrien Béraud612b55b2023-05-29 10:42:04 -040016 */
Adrien Béraud612b55b2023-05-29 10:42:04 -040017#pragma once
18
19#include <atomic>
20#include <thread>
21#include <functional>
22#include <stdexcept>
23#include <condition_variable>
24#include <mutex>
25
26#include <opendht/logger.h>
27
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040028namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040029
30struct ThreadLoopException : public std::runtime_error
31{
32 ThreadLoopException()
33 : std::runtime_error("ThreadLoopException")
34 {}
35};
36
37class ThreadLoop
38{
39public:
40 enum class ThreadState { READY, RUNNING, STOPPING };
41
42 ThreadLoop(std::shared_ptr<dht::log::Logger> logger,
43 const std::function<bool()>& setup,
44 const std::function<void()>& process,
45 const std::function<void()>& cleanup);
46 virtual ~ThreadLoop();
47
48 void start();
49 void exit();
50 virtual void stop();
51 void join();
52 void waitForCompletion(); // thread will stop itself
53
54 bool isRunning() const noexcept;
55 bool isStopping() const noexcept { return state_ == ThreadState::STOPPING; }
56 std::thread::id get_id() const noexcept { return threadId_; }
57
58private:
59 ThreadLoop(const ThreadLoop&) = delete;
60 ThreadLoop(ThreadLoop&&) noexcept = delete;
61 ThreadLoop& operator=(const ThreadLoop&) = delete;
62 ThreadLoop& operator=(ThreadLoop&&) noexcept = delete;
63
64 // These must be provided by users of ThreadLoop
65 std::function<bool()> setup_;
66 std::function<void()> process_;
67 std::function<void()> cleanup_;
68
69 void mainloop(std::thread::id& tid,
70 const std::function<bool()> setup,
71 const std::function<void()> process,
72 const std::function<void()> cleanup);
73
74 std::atomic<ThreadState> state_ {ThreadState::READY};
75 std::thread::id threadId_;
76 std::thread thread_;
77 std::shared_ptr<dht::log::Logger> logger_;
78};
79
80class InterruptedThreadLoop : public ThreadLoop
81{
82public:
83 InterruptedThreadLoop(std::shared_ptr<dht::log::Logger> logger,
84 const std::function<bool()>& setup,
85 const std::function<void()>& process,
86 const std::function<void()>& cleanup)
87 : ThreadLoop::ThreadLoop(logger, setup, process, cleanup)
88 {}
89
90 void stop() override;
91
92 void interrupt() noexcept { cv_.notify_one(); }
93
94 template<typename Rep, typename Period>
95 void wait_for(const std::chrono::duration<Rep, Period>& rel_time)
96 {
97 if (std::this_thread::get_id() != get_id())
98 throw std::runtime_error("can not call wait_for outside thread context");
99
100 std::unique_lock<std::mutex> lk(mutex_);
101 cv_.wait_for(lk, rel_time, [this]() { return isStopping(); });
102 }
103
104 template<typename Rep, typename Period, typename Pred>
105 bool wait_for(const std::chrono::duration<Rep, Period>& rel_time, Pred&& pred)
106 {
107 if (std::this_thread::get_id() != get_id())
108 throw std::runtime_error("can not call wait_for outside thread context");
109
110 std::unique_lock<std::mutex> lk(mutex_);
111 return cv_.wait_for(lk, rel_time, [this, pred] { return isStopping() || pred(); });
112 }
113
114 template<typename Pred>
115 void wait(Pred&& pred)
116 {
117 if (std::this_thread::get_id() != get_id())
118 throw std::runtime_error("Can not call wait outside thread context");
119
120 std::unique_lock<std::mutex> lk(mutex_);
121 cv_.wait(lk, [this, p = std::forward<Pred>(pred)] { return isStopping() || p(); });
122 }
123
124private:
125 std::mutex mutex_;
126 std::condition_variable cv_;
127};
128
Sébastien Blin464bdff2023-07-19 08:02:53 -0400129} // namespace dhtnet