1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
3 #include "base/workqueue.hpp"
4 #include "base/utility.hpp"
5 #include "base/logger.hpp"
6 #include "base/convert.hpp"
7 #include "base/application.hpp"
8 #include "base/exception.hpp"
9 #include <boost/thread/tss.hpp>
12 using namespace icinga;
14 std::atomic<int> WorkQueue::m_NextID(1);
15 boost::thread_specific_ptr<WorkQueue *> l_ThreadWorkQueue;
17 WorkQueue::WorkQueue(size_t maxItems, int threadCount)
18 : m_ID(m_NextID++), m_ThreadCount(threadCount), m_MaxItems(maxItems),
21 /* Initialize logger. */
22 m_StatusTimerTimeout = Utility::GetTime();
24 m_StatusTimer = new Timer();
25 m_StatusTimer->SetInterval(10);
26 m_StatusTimer->OnTimerExpired.connect(std::bind(&WorkQueue::StatusTimerHandler, this));
27 m_StatusTimer->Start();
30 WorkQueue::~WorkQueue()
32 m_StatusTimer->Stop(true);
37 void WorkQueue::SetName(const String& name)
42 String WorkQueue::GetName() const
47 boost::mutex::scoped_lock WorkQueue::AcquireLock()
49 return boost::mutex::scoped_lock(m_Mutex);
53 * Enqueues a task. Tasks are guaranteed to be executed in the order
54 * they were enqueued in except if there is more than one worker thread.
56 void WorkQueue::EnqueueUnlocked(boost::mutex::scoped_lock& lock, std::function<void ()>&& function, WorkQueuePriority priority)
59 Log(LogNotice, "WorkQueue")
60 << "Spawning WorkQueue threads for '" << m_Name << "'";
62 for (int i = 0; i < m_ThreadCount; i++) {
63 m_Threads.create_thread(std::bind(&WorkQueue::WorkerThreadProc, this));
69 bool wq_thread = IsWorkerThread();
72 while (m_Tasks.size() >= m_MaxItems && m_MaxItems != 0)
76 m_Tasks.emplace(std::move(function), priority, ++m_NextTaskID);
78 m_CVEmpty.notify_one();
82 * Enqueues a task. Tasks are guaranteed to be executed in the order
83 * they were enqueued in except if there is more than one worker thread or when
84 * allowInterleaved is true in which case the new task might be run
85 * immediately if it's being enqueued from within the WorkQueue thread.
87 void WorkQueue::Enqueue(std::function<void ()>&& function, WorkQueuePriority priority,
88 bool allowInterleaved)
90 bool wq_thread = IsWorkerThread();
92 if (wq_thread && allowInterleaved) {
98 auto lock = AcquireLock();
99 EnqueueUnlocked(lock, std::move(function), priority);
103 * Waits until all currently enqueued tasks have completed. This only works reliably
104 * when no other thread is enqueuing new tasks when this method is called.
106 * @param stop Whether to stop the worker threads
108 void WorkQueue::Join(bool stop)
110 boost::mutex::scoped_lock lock(m_Mutex);
112 while (m_Processing || !m_Tasks.empty())
113 m_CVStarved.wait(lock);
117 m_CVEmpty.notify_all();
120 m_Threads.join_all();
123 Log(LogNotice, "WorkQueue")
124 << "Stopped WorkQueue threads for '" << m_Name << "'";
129 * Checks whether the calling thread is one of the worker threads
130 * for this work queue.
132 * @returns true if called from one of the worker threads, false otherwise
134 bool WorkQueue::IsWorkerThread() const
136 WorkQueue **pwq = l_ThreadWorkQueue.get();
144 void WorkQueue::SetExceptionCallback(const ExceptionCallback& callback)
146 m_ExceptionCallback = callback;
150 * Checks whether any exceptions have occurred while executing tasks for this
151 * work queue. When a custom exception callback is set this method will always
154 bool WorkQueue::HasExceptions() const
156 boost::mutex::scoped_lock lock(m_Mutex);
158 return !m_Exceptions.empty();
162 * Returns all exceptions which have occurred for tasks in this work queue. When a
163 * custom exception callback is set this method will always return an empty list.
165 std::vector<boost::exception_ptr> WorkQueue::GetExceptions() const
167 boost::mutex::scoped_lock lock(m_Mutex);
172 void WorkQueue::ReportExceptions(const String& facility) const
174 std::vector<boost::exception_ptr> exceptions = GetExceptions();
176 for (const auto& eptr : exceptions) {
177 Log(LogCritical, facility)
178 << DiagnosticInformation(eptr);
181 Log(LogCritical, facility)
182 << exceptions.size() << " error" << (exceptions.size() != 1 ? "s" : "");
185 size_t WorkQueue::GetLength() const
187 boost::mutex::scoped_lock lock(m_Mutex);
189 return m_Tasks.size();
192 void WorkQueue::StatusTimerHandler()
194 boost::mutex::scoped_lock lock(m_Mutex);
196 ASSERT(!m_Name.IsEmpty());
198 size_t pending = m_Tasks.size();
200 double now = Utility::GetTime();
201 double gradient = (pending - m_PendingTasks) / (now - m_PendingTasksTimestamp);
202 double timeToZero = pending / gradient;
206 if (pending > GetTaskCount(5)) {
207 timeInfo = " empty in ";
208 if (timeToZero < 0 || std::isinf(timeToZero))
209 timeInfo += "infinite time, your task handler isn't able to keep up";
211 timeInfo += Utility::FormatDuration(timeToZero);
214 m_PendingTasks = pending;
215 m_PendingTasksTimestamp = now;
217 /* Log if there are pending items, or 5 minute timeout is reached. */
218 if (pending > 0 || m_StatusTimerTimeout < now) {
219 Log(LogInformation, "WorkQueue")
220 << "#" << m_ID << " (" << m_Name << ") "
221 << "items: " << pending << ", "
222 << "rate: " << std::setw(2) << GetTaskCount(60) / 60.0 << "/s "
223 << "(" << GetTaskCount(60) << "/min " << GetTaskCount(60 * 5) << "/5min " << GetTaskCount(60 * 15) << "/15min);"
227 /* Reschedule next log entry in 5 minutes. */
228 if (m_StatusTimerTimeout < now) {
229 m_StatusTimerTimeout = now + 60 * 5;
233 void WorkQueue::RunTaskFunction(const TaskFunction& func)
237 } catch (const std::exception&) {
238 boost::exception_ptr eptr = boost::current_exception();
241 boost::mutex::scoped_lock mutex(m_Mutex);
243 if (!m_ExceptionCallback)
244 m_Exceptions.push_back(eptr);
247 if (m_ExceptionCallback)
248 m_ExceptionCallback(eptr);
252 void WorkQueue::WorkerThreadProc()
254 std::ostringstream idbuf;
255 idbuf << "WQ #" << m_ID;
256 Utility::SetThreadName(idbuf.str());
258 l_ThreadWorkQueue.reset(new WorkQueue *(this));
260 boost::mutex::scoped_lock lock(m_Mutex);
263 while (m_Tasks.empty() && !m_Stopped)
264 m_CVEmpty.wait(lock);
269 if (m_Tasks.size() >= m_MaxItems && m_MaxItems != 0)
270 m_CVFull.notify_all();
272 Task task = m_Tasks.top();
279 RunTaskFunction(task.Function);
281 /* clear the task so whatever other resources it holds are released _before_ we re-acquire the mutex */
291 m_CVStarved.notify_all();
295 void WorkQueue::IncreaseTaskCount()
297 m_TaskStats.InsertValue(Utility::GetTime(), 1);
300 size_t WorkQueue::GetTaskCount(RingBuffer::SizeType span)
302 return m_TaskStats.UpdateAndGetValues(Utility::GetTime(), span);
305 bool icinga::operator<(const Task& a, const Task& b)
307 if (a.Priority < b.Priority)
310 if (a.Priority == b.Priority) {