#include #include #include #include #include #include #include #include #include #include #include #include #include #include"Ev/ThreadPool.hpp" #include"Util/BacktraceException.hpp" #include"Util/make_unique.hpp" namespace { /* RAII class to block all signals while still alive. */ class SigBlocker { private: sigset_t old_set; public: SigBlocker(SigBlocker&&) =delete; SigBlocker(SigBlocker const&) =delete; SigBlocker() { sigset_t blockall; sigfillset(&blockall); pthread_sigmask(SIG_SETMASK, &blockall, &old_set); } ~SigBlocker() { pthread_sigmask(SIG_SETMASK, &old_set, nullptr); } }; } namespace Ev { class ThreadPool::Impl { private: /*-------------------------------------------------------*/ /* These objects are handled only by the * main thread. */ /* Background threads. */ std::vector threads; /* Number of pending added tasks. */ std::size_t num_tasks; /* An ev_io watcher that is waiting for * the read pipe. */ std::unique_ptr io_waiter; /*-------------------------------------------------------*/ /* This is only set during initialization. */ int pipe_write; int pipe_read; /*-------------------------------------------------------*/ /* These objects are shared across threads. * Hold the mutex while using these! */ std::mutex mtx; /* Used to wake up a background thread if * there is work to be done. */ std::condition_variable cnd; /* Set to true if this is being destructed. */ bool shutdown; /* Queue of commands to execute in the * background thread. */ std::queue()>> work_queue; /* Queue of results to execute in the * main thread. */ std::queue> result_queue; private: /* Gets from the work queue. * Precondition: the mutex must be locked. */ std::function()> get_work(std::unique_lock& locker) { while (work_queue.empty() && !shutdown) cnd.wait(locker); if (shutdown) return nullptr; auto ret = std::move(work_queue.front()); work_queue.pop(); return ret; } /* Puts to the result queue. * Precondition: the mutex must be locked. */ void put_result(std::function result) { result_queue.emplace(std::move(result)); /* Wake up main thread. */ auto c = char(1); auto res = ssize_t(); do { res = write(pipe_write, &c, 1); } while (res < 0 && errno == EINTR); } /* Run at each backgrond thread. */ void background() { auto locker = std::unique_lock(mtx); for (;;) { auto work = get_work(locker); locker.unlock(); if (!work) return; auto result = work(); work = nullptr; locker.lock(); put_result(std::move(result)); } } /* Run to handle an ev_io event. */ void io_handler(EV_P) { /* Remove one notification from the pipe. * FIXME * It would theoretically be better to just * read as much as is available until EAGAIN * or EWOULDBLOCK, and then pull that many * from the queue, but that optimization can * be done later. */ auto c = char(); auto res = ssize_t(); do { res = read(pipe_read, &c, 1); } while (res < 0 && errno == EINTR); /* Grab one. */ auto result = ([this](){ auto locker = std::unique_lock(mtx); auto ret = std::move(result_queue.front()); result_queue.pop(); return ret; })(); --num_tasks; if (num_tasks == 0) { /* No more tasks, so we can stop the watcher. * This is important as we have the rule that * all watchers must stop on a breaking signal. */ ev_io_stop(EV_A_ io_waiter.get()); io_waiter = nullptr; } result(); } static void io_handler_static(EV_P_ ev_io* raw_io_waiter, int revents) { auto self = (Impl*)raw_io_waiter->data; return self->io_handler(EV_A); } public: Impl() { num_tasks = 0; shutdown = false; int pipes[2]; auto pipe_res = pipe(pipes); if (pipe_res < 0) { throw Util::BacktraceException(std::string("Ev::ThreadPool: pipe:") + strerror(errno) ); } pipe_read = pipes[0]; pipe_write = pipes[1]; { /* Make read end non-blocking. */ auto flags = fcntl(pipe_read, F_GETFL); flags |= O_NONBLOCK; fcntl(pipe_read, F_SETFL, flags); } /* Launch threads with all signals blocked. * On exit from this function, the current * thread has its signal mask restored. */ SigBlocker blocker; for (auto i = 0; i < 16; ++i) threads.emplace_back([this]() { background(); }); } void add(std::function()> work) { assert(work); { auto locker = std::unique_lock(mtx); work_queue.emplace(std::move(work)); } cnd.notify_one(); ++num_tasks; if (!io_waiter) { io_waiter = Util::make_unique(); ev_io_init( io_waiter.get() , &io_handler_static , pipe_read , EV_READ ); io_waiter->data = this; ev_io_start(EV_DEFAULT_ io_waiter.get()); } } ~Impl() { if (io_waiter) ev_io_stop(EV_DEFAULT_ io_waiter.get()); /* Signal the shutdown. */ { auto locker = std::unique_lock(mtx); shutdown = true; } cnd.notify_all(); /* Wait for all threads to finish. */ for (auto& t : threads) t.join(); } }; ThreadPool::ThreadPool() : pimpl(Util::make_unique()) { } ThreadPool::~ThreadPool() { } void ThreadPool::add(std::function()> work) { return pimpl->add(std::move(work)); } }