I've created a JobManager
class that manages the execution of submitted Jobs
on periodic intervals. While the JobManager
is running, the API allows the user to:
- Add or Remove a
Job
to/from theJobManager
- Change a
Job
's interval, function parameter, and function - Signal a
Job
to run if it is waiting
The testing I have done seems to denote that the class is working correctly. What stands out to you as a bad practice and or an inefficiency here? What potential problems do you see with using this? The Job
could be used by itself but I haven't implemented the rule of 5 with it.
// The Job class
#pragma once
#include <chrono>
#include <any>
#include <thread>
#include <functional>
#include <condition_variable>
#include <mutex>
#include <algorithm>
#include <list>
namespace engine {
using job_clock = std::chrono::system_clock;
using job_func = std::function<void(const std::any &, bool)>;
using job_interval = std::chrono::system_clock::duration;
using job_time = std::chrono::system_clock::time_point;
class Job {
public:
Job(uint32_t job_id, job_func &&func, const job_interval &interval,
const std::any ¶m)
: id(job_id), m_func(func), m_interval(interval), m_param(param),
m_lock(std::make_shared<std::mutex>()),
m_cv(std::make_shared<std::condition_variable>())
{
}
void Start(bool now)
{
if (!m_thread) {
m_thread = std::make_unique<std::thread>(
[this](bool now) { Monitor(now); }, now);
}
}
void Stop()
{
if (m_thread) {
m_quit = true;
if (m_waiting && m_cv) {
m_cv->notify_one();
}
m_thread->join();
m_thread = nullptr;
}
}
void SetParam(const std::any ¶m)
{
if (m_thread && m_lock) {
std::lock_guard<std::mutex> lk(*m_lock);
m_param = param;
}
else {
m_param = param;
}
}
void SetInterval(job_interval interval)
{
if (m_thread && m_lock) {
std::lock_guard<std::mutex> lk(*m_lock);
m_interval = interval;
}
else {
m_interval = interval;
}
}
void SetFunc(job_func &&func)
{
if (m_thread && m_lock) {
std::lock_guard<std::mutex> lk(*m_lock);
m_func = func;
}
else {
m_func = func;
}
}
bool Signal()
{
if (m_waiting && m_cv) {
m_signaled = true;
m_cv->notify_one();
return true;
}
return false;
}
[[nodiscard]] bool Active() const
{
return m_active;
}
[[nodiscard]] bool Waiting() const
{
return m_waiting;
}
const uint32_t id;
private:
void Monitor(bool now)
{
m_active = true;
if (now) {
m_func(m_param, false);
}
while (!m_quit) {
job_time time = job_clock::now() + m_interval;
{
if (m_lock && m_cv) {
m_waiting = true;
std::unique_lock<std::mutex> lk(*m_lock);
m_cv->wait_until(lk, time);
m_waiting = false;
}
}
if (!m_quit && m_lock) {
std::lock_guard<std::mutex> lk(*m_lock);
m_func(m_param, m_signaled);
m_signaled = false;
}
}
m_active = false;
}
std::shared_ptr<std::mutex> m_lock{nullptr};
std::shared_ptr<std::condition_variable> m_cv{nullptr};
std::shared_ptr<std::thread> m_thread{nullptr};
bool m_active{false};
bool m_signaled{false};
bool m_waiting{false};
bool m_quit{false};
job_func m_func;
job_interval m_interval;
std::any m_param;
};
} // namespace engine
// The JobManager class
#include <engine/Job.hpp>
namespace engine {
class JobManager final {
public:
JobManager(std::initializer_list<Job> jobs) : m_jobs(jobs) {}
JobManager() = default;
JobManager &operator=(const JobManager &rhs) = delete;
JobManager(const JobManager &rhs) = delete;
JobManager &operator=(JobManager &&rhs) = delete;
JobManager(JobManager &&rhs) = delete;
~JobManager()
{
Stop();
}
void Add(std::initializer_list<Job> jobs)
{
std::lock_guard<std::mutex> lk(m_lock);
for (auto &job : jobs) {
if (AddJobIfNonExistent(job)) {
m_jobs.back().Start(false);
}
}
}
void Add(const Job &job, bool run_now = false)
{
std::lock_guard<std::mutex> lk(m_lock);
if (AddJobIfNonExistent(job)) {
m_jobs.back().Start(run_now);
}
}
void Remove(uint32_t id)
{
std::lock_guard<std::mutex> lk(m_lock);
auto job = FindJobByID(id);
if (job != m_jobs.end()) {
job->Stop();
m_jobs.erase(job);
}
}
void SetJobParam(uint32_t id, const std::any ¶m)
{
std::lock_guard<std::mutex> lk(m_lock);
auto job = FindJobByID(id);
if (job != m_jobs.end()) {
job->SetParam(param);
}
}
void SetJobInterval(uint32_t id, job_interval interval)
{
std::lock_guard<std::mutex> lk(m_lock);
auto job = FindJobByID(id);
if (job != m_jobs.end()) {
job->SetInterval(interval);
}
}
void SetJobFunc(uint32_t id, job_func &&func)
{
std::lock_guard<std::mutex> lk(m_lock);
auto job = FindJobByID(id);
if (job != m_jobs.end()) {
job->SetFunc(std::move(func));
}
}
void Signal(uint32_t id)
{
std::lock_guard<std::mutex> lk(m_lock);
auto job = FindJobByID(id);
if (job != m_jobs.end()) {
job->Signal();
}
}
void Start()
{
std::lock_guard<std::mutex> lk(m_lock);
auto StartMonitoringJob = [](Job &job) { job.Start(false); };
ForEachJob(StartMonitoringJob);
}
void Stop()
{
std::lock_guard<std::mutex> lk(m_lock);
auto JoinAndBlock = [](Job &job) { job.Stop(); };
ForEachJob(JoinAndBlock);
}
private:
std::list<Job> m_jobs;
std::mutex m_lock;
std::list<Job>::iterator FindJobByID(uint32_t id)
{
auto IDMatch = [id](const Job &j) { return j.id == id; };
return std::find_if(m_jobs.begin(), m_jobs.end(), IDMatch);
}
bool AddJobIfNonExistent(const Job &job)
{
if (FindJobByID(job.id) == m_jobs.end()) {
m_jobs.push_back(job);
return true;
}
return false;
}
void ForEachJob(std::function<void(Job &)> predicate)
{
std::for_each(m_jobs.begin(), m_jobs.end(), predicate);
}
};
} // namespace engine
#include <spdlog/spdlog.h>
#include <engine/job_manager.hpp>
#define _dbg(f, ...) spdlog::info(f, ##__VA_ARGS__)
uint32_t JOB1_ID = 1;
uint32_t JOB2_ID = 2;
struct Job1Info {
int integer;
std::string str;
};
struct Job2Info {
int some_value;
bool other_value;
};
void Job1(std::any param, bool signaled)
{
static int called = 1;
auto info = std::any_cast<Job1Info>(param);
_dbg("Job1: signaled={}, called={}, Job1Info({}, {})", signaled, called++,
info.integer, info.str);
}
void Job2(std::any param, bool signaled)
{
static int called = 1;
auto info = std::any_cast<Job2Info>(param);
_dbg("Job2: signaled={}, called={}, Job2Info({}, {})", signaled, called++,
info.some_value, info.other_value);
}
int main(int argc, char **argv)
{
using namespace std::literals::chrono_literals;
using namespace engine;
Job1Info info1{10, std::string("hello")};
Job2Info info2{50, false};
Job job1(JOB1_ID, job_func{Job1}, 5000ms, std::make_any<Job1Info>(info1));
Job job2(JOB2_ID, job_func{Job2}, 10000ms, std::make_any<Job2Info>(info2));
JobManager manager({job1, job2});
_dbg("Start");
manager.Start();
std::this_thread::sleep_for(20000ms);
manager.SetJobParam(JOB2_ID, std::make_any<Job2Info>(30, true));
manager.Remove(JOB1_ID);
std::this_thread::sleep_for(10000ms);
manager.Add(job1, true);
std::this_thread::sleep_for(20000ms);
manager.SetJobParam(JOB1_ID, std::make_any<Job1Info>(10, "something"));
std::this_thread::sleep_for(20000ms);
manager.Stop();
_dbg("end");
}
[2022-06-28 16:43:14.263] [info] Start
[2022-06-28 16:43:19.289] [info] Job1: signaled=false, called=1, Job1Info(10, hello)
[2022-06-28 16:43:24.291] [info] Job2: signaled=false, called=1, Job2Info(50, false)
[2022-06-28 16:43:24.291] [info] Job1: signaled=false, called=2, Job1Info(10, hello)
[2022-06-28 16:43:29.305] [info] Job1: signaled=false, called=3, Job1Info(10, hello)
[2022-06-28 16:43:34.307] [info] Job2: signaled=false, called=2, Job2Info(30, true)
[2022-06-28 16:43:44.305] [info] Job1: signaled=false, called=4, Job1Info(10, hello)
[2022-06-28 16:43:44.318] [info] Job2: signaled=false, called=3, Job2Info(30, true)
[2022-06-28 16:43:49.314] [info] Job1: signaled=false, called=5, Job1Info(10, hello)
[2022-06-28 16:43:54.325] [info] Job2: signaled=false, called=4, Job2Info(30, true)
[2022-06-28 16:43:54.325] [info] Job1: signaled=false, called=6, Job1Info(10, hello)
[2022-06-28 16:43:59.342] [info] Job1: signaled=false, called=7, Job1Info(10, hello)
[2022-06-28 16:44:04.327] [info] Job2: signaled=false, called=5, Job2Info(30, true)
[2022-06-28 16:44:04.358] [info] Job1: signaled=false, called=8, Job1Info(10, something)
[2022-06-28 16:44:09.374] [info] Job1: signaled=false, called=9, Job1Info(10, something)
[2022-06-28 16:44:14.342] [info] Job2: signaled=false, called=6, Job2Info(30, true)
[2022-06-28 16:44:14.388] [info] Job1: signaled=false, called=10, Job1Info(10, something)
[2022-06-28 16:44:19.397] [info] Job1: signaled=false, called=11, Job1Info(10, something)
[2022-06-28 16:44:24.322] [info] end