2#ifndef LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_DETAIL_SINGLETHREADEXECUTOR_HPP_INCLUDED
3#define LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_DETAIL_SINGLETHREADEXECUTOR_HPP_INCLUDED
14#if !defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__)
15#include <condition_variable>
18#include <emscripten/emscripten.h>
40#if !defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__)
68 std::unique_lock<std::mutex> lock(
m_mutex);
69 if (
m_stop.load(std::memory_order_acquire))
return;
72 case QueuePolicy::DropNewest:
75 case QueuePolicy::DropOldest:
81 case QueuePolicy::Block:
82 m_cv.wait(lock, [
this]() {
85 m_stop.load(std::memory_order_acquire);
87 if (
m_stop.load(std::memory_order_acquire))
return;
91 m_queue.push_back(std::move(task));
101 std::unique_lock<std::mutex> lock(
m_mutex);
102 m_cv.wait(lock, [
this]() {
114 bool notify_worker =
false;
116 std::lock_guard<std::mutex> lock(
m_mutex);
119 m_stop.store(
true, std::memory_order_release);
120 notify_worker =
true;
126 if (
m_worker.joinable() &&
m_worker.get_id() != std::this_thread::get_id()) {
136 std::lock_guard<std::mutex> lock(
m_mutex);
137 if (
m_stop.load(std::memory_order_acquire))
return;
147 std::lock_guard<std::mutex> lock(
m_mutex);
148 if (
m_stop.load(std::memory_order_acquire))
return;
176 std::function<void()> task;
178 std::unique_lock<std::mutex> lock(
m_mutex);
179 m_cv.wait(lock, [
this]() {
180 return !
m_queue.empty() ||
m_stop.load(std::memory_order_acquire);
182 if (
m_stop.load(std::memory_order_acquire) &&
m_queue.empty()) {
186 task = std::move(
m_queue.front());
199 std::lock_guard<std::mutex> lock(
m_mutex);
219class SingleThreadExecutor {
221 SingleThreadExecutor()
222 : m_state(new State()) {}
224 ~SingleThreadExecutor() {
228 SingleThreadExecutor(
const SingleThreadExecutor&) =
delete;
229 SingleThreadExecutor& operator=(
const SingleThreadExecutor&) =
delete;
230 SingleThreadExecutor(SingleThreadExecutor&&) =
delete;
231 SingleThreadExecutor& operator=(SingleThreadExecutor&&) =
delete;
234 void add_task(std::function<
void()> task) {
236 const std::shared_ptr<State> state = m_state;
237 bool schedule =
false;
240 bool drain_for_capacity =
false;
242 std::lock_guard<std::mutex> lock(state->mutex);
243 if (state->shutdown_requested)
return;
245 if (state->max_queue_size > 0 &&
246 state->queue.size() >= state->max_queue_size) {
247 switch (state->overflow_policy) {
248 case QueuePolicy::DropNewest:
249 ++state->dropped_tasks;
251 case QueuePolicy::DropOldest:
252 if (!state->queue.empty()) {
253 state->queue.pop_front();
254 ++state->dropped_tasks;
257 case QueuePolicy::Block:
258 drain_for_capacity =
true;
263 if (drain_for_capacity &&
264 state->max_queue_size > 0 &&
265 state->queue.size() >= state->max_queue_size) {
269 state->queue.push_back(std::move(task));
270 schedule = !state->scheduled;
271 state->scheduled = state->scheduled || schedule;
280 schedule_drain(state);
288 drain_state(m_state);
296 const std::shared_ptr<State> state = m_state;
298 std::lock_guard<std::mutex> lock(state->mutex);
299 if (state->shutdown_requested)
return;
300 state->shutdown_requested =
true;
308 void set_max_queue_size(std::size_t size) {
309 std::lock_guard<std::mutex> lock(m_state->mutex);
310 if (m_state->shutdown_requested)
return;
311 m_state->max_queue_size = size;
318 std::lock_guard<std::mutex> lock(m_state->mutex);
319 if (m_state->shutdown_requested)
return;
320 m_state->overflow_policy = policy;
323 std::size_t dropped_tasks() const noexcept {
324 return m_state->dropped_tasks.load(std::memory_order_relaxed);
327 void reset_dropped_tasks() noexcept {
328 m_state->dropped_tasks.store(0, std::memory_order_relaxed);
338 , shutdown_requested(false) {}
340 std::deque<std::function<void()>> queue;
342 std::size_t max_queue_size;
344 std::atomic<std::size_t> dropped_tasks;
346 bool shutdown_requested;
349 std::shared_ptr<State> m_state;
351 static void schedule_drain(
const std::shared_ptr<State>& state) {
352 std::shared_ptr<State>* token =
new std::shared_ptr<State>(state);
353 emscripten_async_call(&SingleThreadExecutor::drain_thunk, token, 0);
356 static void drain_thunk(
void* arg) {
357 std::unique_ptr<std::shared_ptr<State>> token(
358 static_cast<std::shared_ptr<State>*
>(arg));
362 static void drain_state(
const std::shared_ptr<State>& state) {
364 std::function<void()> task;
366 std::lock_guard<std::mutex> lock(state->mutex);
367 if (state->queue.empty()) {
368 state->scheduled =
false;
371 task = std::move(state->queue.front());
372 state->queue.pop_front();
Per-instance single-thread executor for isolated async logging.
void wait()
Block until the queue is empty and no active tasks remain.
std::atomic< std::size_t > m_dropped_tasks
std::atomic< bool > m_shutdown_done
SingleThreadExecutor & operator=(SingleThreadExecutor &&)=delete
void set_max_queue_size(std::size_t size)
Change the maximum queue size (0 disables the limit).
std::atomic< bool > m_stop
std::size_t m_max_queue_size
SingleThreadExecutor & operator=(const SingleThreadExecutor &)=delete
~SingleThreadExecutor()
Destructor drains and joins the worker thread.
void add_task(std::function< void()> task)
Enqueue a task for asynchronous execution.
SingleThreadExecutor(SingleThreadExecutor &&)=delete
std::size_t dropped_tasks() const noexcept
Return the number of tasks dropped by the overflow policy.
QueuePolicy m_overflow_policy
void set_queue_policy(QueuePolicy policy)
Change the queue overflow policy.
std::deque< std::function< void()> > m_queue
SingleThreadExecutor(const SingleThreadExecutor &)=delete
std::atomic< std::size_t > m_active_tasks
std::condition_variable m_cv
void reset_dropped_tasks() noexcept
Reset the drop counter to zero.
SingleThreadExecutor()
Construct and immediately start the worker thread.
void shutdown()
Stop accepting new tasks, drain remaining, and join the worker thread.
QueuePolicy
Queue overflow handling policy used by TaskExecutor and SingleThreadExecutor.
@ Block
Producers wait until capacity is available.
The primary namespace for the LogIt++ library.