blob: b8e8c3cd19bb2d52ec5078e8b7f2c86165a293b6 [file] [log] [blame]
/*
* Copyright (C) 2004-2021 Savoir-faire Linux Inc.
*
* Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com>
* Author: Eloi Bail <Eloi.Bail@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include <atomic>
#include <thread>
#include <functional>
#include <stdexcept>
#include <condition_variable>
#include <mutex>
namespace jami {
struct ThreadLoopException : public std::runtime_error
{
ThreadLoopException()
: std::runtime_error("ThreadLoopException")
{}
};
class ThreadLoop
{
public:
enum class ThreadState { READY, RUNNING, STOPPING };
ThreadLoop(const std::function<bool()>& setup,
const std::function<void()>& process,
const std::function<void()>& cleanup);
virtual ~ThreadLoop();
void start();
void exit();
virtual void stop();
void join();
void waitForCompletion(); // thread will stop itself
bool isRunning() const noexcept;
bool isStopping() const noexcept { return state_ == ThreadState::STOPPING; }
std::thread::id get_id() const noexcept { return threadId_; }
private:
ThreadLoop(const ThreadLoop&) = delete;
ThreadLoop(ThreadLoop&&) noexcept = delete;
ThreadLoop& operator=(const ThreadLoop&) = delete;
ThreadLoop& operator=(ThreadLoop&&) noexcept = delete;
// These must be provided by users of ThreadLoop
std::function<bool()> setup_;
std::function<void()> process_;
std::function<void()> cleanup_;
void mainloop(std::thread::id& tid,
const std::function<bool()> setup,
const std::function<void()> process,
const std::function<void()> cleanup);
std::atomic<ThreadState> state_ {ThreadState::READY};
std::thread::id threadId_;
std::thread thread_;
};
class InterruptedThreadLoop : public ThreadLoop
{
public:
InterruptedThreadLoop(const std::function<bool()>& setup,
const std::function<void()>& process,
const std::function<void()>& cleanup)
: ThreadLoop::ThreadLoop(setup, process, cleanup)
{}
void stop() override;
void interrupt() noexcept { cv_.notify_one(); }
template<typename Rep, typename Period>
void wait_for(const std::chrono::duration<Rep, Period>& rel_time)
{
if (std::this_thread::get_id() != get_id())
throw std::runtime_error("can not call wait_for outside thread context");
std::unique_lock<std::mutex> lk(mutex_);
cv_.wait_for(lk, rel_time, [this]() { return isStopping(); });
}
template<typename Rep, typename Period, typename Pred>
bool wait_for(const std::chrono::duration<Rep, Period>& rel_time, Pred&& pred)
{
if (std::this_thread::get_id() != get_id())
throw std::runtime_error("can not call wait_for outside thread context");
std::unique_lock<std::mutex> lk(mutex_);
return cv_.wait_for(lk, rel_time, [this, pred] { return isStopping() || pred(); });
}
template<typename Pred>
void wait(Pred&& pred)
{
if (std::this_thread::get_id() != get_id())
throw std::runtime_error("Can not call wait outside thread context");
std::unique_lock<std::mutex> lk(mutex_);
cv_.wait(lk, [this, p = std::forward<Pred>(pred)] { return isStopping() || p(); });
}
private:
std::mutex mutex_;
std::condition_variable cv_;
};
} // namespace jami