BareGit
#include "thread_pool.h"

#include <utility>

namespace
{

// Terminate before packaged_task can capture an unexpected exception.
void invokeTask(std::move_only_function<void()>& task) noexcept
{
    task();
}

}

ThreadPool::ThreadPool(std::size_t worker_count)
    : workers(worker_count)
{}

mw::E<std::unique_ptr<ThreadPool>> ThreadPool::create(std::size_t worker_count)
{
    if(worker_count == 0)
    {
        return std::unexpected(mw::runtimeError(
            "Thread pool capacity must be positive"));
    }
    auto pool = std::unique_ptr<ThreadPool>(new ThreadPool(worker_count));
    for(auto& worker : pool->workers)
    {
        worker.thread = std::thread(&ThreadPool::run, pool.get(),
                                    std::ref(worker));
    }
    return pool;
}

ThreadPool::~ThreadPool()
{
    stop();
}

void ThreadPool::stop()
{
    {
        std::lock_guard lock(mutex);
        stopping = true;
    }
    for(auto& worker : workers)
    {
        worker.work_ready.notify_one();
    }
    for(auto& worker : workers)
    {
        if(worker.thread.joinable())
        {
            worker.thread.join();
        }
    }
}

mw::E<std::optional<std::future<void>>> ThreadPool::trySubmit(
    std::move_only_function<void()> task)
{
    if(!task)
    {
        return std::unexpected(mw::runtimeError(
            "Thread pool task must not be empty"));
    }
    std::unique_lock lock(mutex);
    if(stopping)
    {
        return std::nullopt;
    }
    for(auto& worker : workers)
    {
        if(!worker.busy)
        {
            std::packaged_task<void()> packaged(
                std::bind_front(invokeTask, std::move(task)));
            auto result = packaged.get_future();
            worker.task = std::move(packaged);
            worker.busy = true;
            ++outstanding;
            lock.unlock();
            worker.work_ready.notify_one();
            return std::optional<std::future<void>>(std::move(result));
        }
    }
    return std::nullopt;
}

void ThreadPool::waitIdle()
{
    std::unique_lock lock(mutex);
    while(outstanding != 0)
    {
        idle.wait(lock);
    }
}

void ThreadPool::run(Worker& worker)
{
    while(true)
    {
        std::packaged_task<void()> task;
        {
            std::unique_lock lock(mutex);
            while(!worker.task.valid() && !stopping)
            {
                worker.work_ready.wait(lock);
            }
            if(!worker.task.valid())
            {
                return;
            }
            task = std::move(worker.task);
        }
        task();
        task = {};
        {
            std::lock_guard lock(mutex);
            worker.busy = false;
            --outstanding;
            if(outstanding == 0)
            {
                idle.notify_all();
            }
        }
    }
}