2#ifndef LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_DETAIL_TASKEXECUTOR_HPP_INCLUDED
3#define LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_DETAIL_TASKEXECUTOR_HPP_INCLUDED
11#if defined(__EMSCRIPTEN__) && !defined(__EMSCRIPTEN_PTHREADS__)
14 #include <emscripten/emscripten.h>
19 #include <condition_variable>
26#if !defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__)
27 #ifdef LOGIT_USE_MPSC_RING
34namespace logit {
namespace detail {
36# if defined(__EMSCRIPTEN__) && !defined(__EMSCRIPTEN_PTHREADS__)
54 void add_task(std::function<
void()> task) {
56 bool schedule =
false;
60 std::lock_guard<std::mutex> lk(m_mutex);
63 case QueuePolicy::DropNewest:
66 case QueuePolicy::DropOldest:
67 if (!m_tasks.empty()) {
72 case QueuePolicy::Block:
78 m_tasks.emplace_back(std::move(task));
79 schedule = !m_scheduled;
80 m_scheduled = m_scheduled || schedule;
84 m_tasks.emplace_back(std::move(task));
85 schedule = !m_scheduled;
86 m_scheduled = m_scheduled || schedule;
93 emscripten_async_call(&TaskExecutor::drain_thunk,
this, 0);
98 void wait() { drain(); }
104 std::lock_guard<std::mutex> lk(m_mutex);
110 std::lock_guard<std::mutex> lk(m_mutex);
128 m_scheduled(
false) {}
135 std::deque<std::function<void()>> m_tasks;
142 static void drain_thunk(
void* arg) {
148 std::function<void()> task;
150 std::lock_guard<std::mutex> lk(m_mutex);
151 if (m_tasks.empty()) {
155 task = std::move(m_tasks.front());
188# ifndef LOGIT_USE_MPSC_RING
190 if (
m_stop_flag.load(std::memory_order_acquire))
return;
193 case QueuePolicy::DropNewest:
196 case QueuePolicy::DropOldest:
202 case QueuePolicy::Block:
207 if (
m_stop_flag.load(std::memory_order_acquire))
return;
217 std::function<void()> local_task = std::move(task);
228 if (policy == QueuePolicy::Block &&
232 std::unique_lock<std::mutex> lk(m_cv_mutex);
242 if (m_mpsc_queue.try_push(std::move(local_task))) {
249 case QueuePolicy::DropNewest:
254 case QueuePolicy::DropOldest:
261 case QueuePolicy::Block: {
262 std::unique_lock<std::mutex> lk(m_cv_mutex);
274# ifndef LOGIT_USE_MPSC_RING
284 return ((queue_empty_() &&
294# ifndef LOGIT_USE_MPSC_RING
296 m_stop_flag.store(
true, std::memory_order_release);
305 m_stop_flag.store(
true, std::memory_order_release);
320 if (
m_stop_flag.load(std::memory_order_acquire))
return;
321# ifdef LOGIT_USE_MPSC_RING
323 m_resizing.store(
true, std::memory_order_release);
324 const auto deadline = std::chrono::steady_clock::now() +
325 std::chrono::seconds(1);
328 if (!wait_until_producers_paused_(deadline)) {
329 m_resizing.store(
false, std::memory_order_release);
330 m_resize_cv.notify_all();
339 if (!wait_until_idle_(deadline)) {
340 m_resizing.store(
false, std::memory_order_release);
341 m_resize_cv.notify_all();
347 m_stop_flag.store(
true, std::memory_order_relaxed);
359 const std::size_t cap =
361 m_mpsc_queue =
MpscRingAny<std::function<void()>>(cap);
368 m_stop_flag.store(
false, std::memory_order_relaxed);
372 m_resizing.store(
false, std::memory_order_release);
373 m_resize_cv.notify_all();
383 if (
m_stop_flag.load(std::memory_order_acquire))
return;
399 #ifndef LOGIT_USE_MPSC_RING
413 std::condition_variable m_cv;
414 std::mutex m_cv_mutex;
416 std::atomic<bool> m_resizing;
417 std::condition_variable m_resize_cv;
418 std::atomic<std::size_t> m_active_producers;
432 #ifndef LOGIT_USE_MPSC_RING
434 std::function<void()> task;
459 bool drained_any =
false;
460 std::function<void()> task;
468 if (!m_mpsc_queue.try_pop(task)) {
481 if (queue_empty_() &&
m_active_tasks.load(std::memory_order_relaxed) == 0) {
491 std::unique_lock<std::mutex> lk(m_cv_mutex);
492 if (
m_stop_flag.load(std::memory_order_acquire) && queue_empty_()) {
495 m_cv.wait_for(lk, std::chrono::milliseconds(1));
501 #ifdef LOGIT_USE_MPSC_RING
502 bool queue_empty_() const noexcept {
503 return m_mpsc_queue.empty();
506 bool wait_until_idle_(std::chrono::steady_clock::time_point deadline) {
509 return ((queue_empty_() &&
515 void enter_producer_() {
517 if (m_resizing.load(std::memory_order_acquire)) {
518 std::unique_lock<std::mutex> lk(m_cv_mutex);
519 m_resize_cv.wait(lk, [
this]() {
520 return !m_resizing.load(std::memory_order_acquire);
525 m_active_producers.fetch_add(1, std::memory_order_acq_rel);
526 if (!m_resizing.load(std::memory_order_acquire)) {
533 void leave_producer_() {
534 if (m_active_producers.fetch_sub(1, std::memory_order_acq_rel) == 1 &&
535 m_resizing.load(std::memory_order_acquire)) {
536 m_resize_cv.notify_all();
540 bool wait_until_producers_paused_(std::chrono::steady_clock::time_point deadline) {
541 std::unique_lock<std::mutex> lk(m_cv_mutex);
542 return m_resize_cv.wait_until(lk, deadline, [
this]() {
543 return m_active_producers.load(std::memory_order_acquire) == 0;
549 #ifndef LOGIT_USE_MPSC_RING
557 m_active_producers(0),
564 m_mpsc_queue(m_default_ring_cap)
Bounded MPSC ring buffer with arbitrary capacity (C++11).
Thread-safe task executor backed by a dedicated worker thread.
std::atomic< std::size_t > m_dropped_tasks
std::thread m_worker_thread
void set_max_queue_size(std::size_t size)
Update the maximum queue size (0 disables the limit).
std::deque< std::function< void()> > m_tasks_queue
TaskExecutor(const TaskExecutor &)=delete
std::atomic< bool > m_stop_flag
TaskExecutor & operator=(const TaskExecutor &)=delete
std::condition_variable m_queue_condition
void reset_dropped_tasks() noexcept
Reset the overflow counter to zero.
TaskExecutor & operator=(TaskExecutor &&)=delete
std::atomic< QueuePolicy > m_overflow_policy
void shutdown()
Stop the worker thread and drain outstanding tasks.
std::atomic< std::size_t > m_active_tasks
TaskExecutor(TaskExecutor &&)=delete
void set_queue_policy(QueuePolicy policy)
Change the overflow policy for newly submitted tasks.
std::size_t dropped_tasks() const noexcept
Return the number of tasks dropped by the overflow policy.
void add_task(std::function< void()> task)
Enqueue a task for asynchronous execution.
std::mutex m_lifecycle_mutex
Serializes shutdown with lifecycle-changing operations.
void wait()
Block until the queue is empty or a shutdown is requested.
static TaskExecutor & get_instance()
Returns the global executor instance.
std::size_t m_max_queue_size
#define LOGIT_TASK_EXECUTOR_DRAIN_BUDGET
Maximum number of tasks drained per worker iteration in ring-buffer builds.
#define LOGIT_TASK_EXECUTOR_DEFAULT_RING_CAPACITY
Default capacity for the task executor ring buffer when unlimited is requested.
#define LOGIT_TASK_EXECUTOR_BLOCK_WAIT_USEC
Defines the sleep duration (in microseconds) used by TaskExecutor when blocking producers.
QueuePolicy
Queue overflow handling policy used by TaskExecutor and SingleThreadExecutor.
The primary namespace for the LogIt++ library.