Repository: bitcoin/bitcoin
File: src/util/threadpool.h (C/C++)

Code before:
        WITH_LOCK(m_mutex, Assume(m_work_queue.empty()));
        // Note: m_interrupt is left true until next Start()
    }

    /**
     * @brief Enqueues a new task for asynchronous execution.
     *
     * Returns a `std::future` that provides the task's result or propagates
     * any exception it throws.
     * Note: Ignoring the returned future requires guarding the task against
     * uncaught exceptions, as they would otherwise be silently discarded.
     */
    template <class F> [[nodiscard]]
    util::Expected<std::future<std::invoke_result_t<F>>, std::string> Submit(F&& fn) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
    {
        std::packaged_task<std::invoke_result_t<F>()> task{std::forward<F>(fn)};
        auto future{task.get_future()};
        {
            LOCK(m_mutex);
            if (m_interrupt || m_workers.empty()) {
                return util::Unexpected<std::string>("No active workers; cannot accept new tasks");
            }
            m_work_queue.emplace(std::move(task));
        }
        m_cv.notify_one();
        return util::Expected<decltype(future), std::string>(std::move(future));
    }

    /**
     * @brief Execute a single queued task synchronously.
     * Removes one task from the queue and executes it on the calling thread.
     */
    void ProcessTask() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
    {
        std::packaged_task<void()> task;
        {
            LOCK(m_mutex);
            if (m_work_queue.empty()) return;

            // Pop the task
            task = std::move(m_work_queue.front());
            m_work_queue.pop();
        }
        task();
    }

    /**
     * @brief Stop accepting new tasks and begin asynchronous shutdown.
     *
     * Wakes all worker threads so they can drain the queue and exit.
     * Unlike Stop(), this function does not wait for threads to finish.

Review comment (suggestion):
nit: Can replace with:
```C++
        return {std::move(future)};
```

Code after:
        // Note: m_interrupt is left true until next Start()
    }

    enum class SubmitError {
        Inactive,
        Interrupted,
    };

    /**
     * @brief Enqueues a new task for asynchronous execution.
     *
     * @param  fn Callable to execute asynchronously.
     * @return On success, a future containing fn's result.
     *         On failure, an error indicating why the task was rejected:
     *         - SubmitError::Inactive: Pool has no workers (never started or already stopped).
     *         - SubmitError::Interrupted: Pool task acceptance has been interrupted.
     *
     * Thread-safe: Can be called from any thread, including within the provided 'fn' callable.
     *
     * @warning Ignoring the returned future requires guarding the task against
     *          uncaught exceptions, as they would otherwise be silently discarded.
     */
    template <class F>
    [[nodiscard]] util::Expected<std::future<std::invoke_result_t<F>>, SubmitError> Submit(F&& fn) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
    {
        std::packaged_task<std::invoke_result_t<F>()> task{std::forward<F>(fn)};
        auto future{task.get_future()};
        {
            LOCK(m_mutex);
            if (m_workers.empty()) return util::Unexpected{SubmitError::Inactive};
            if (m_interrupt) return util::Unexpected{SubmitError::Interrupted};

            m_work_queue.emplace(std::move(task));
        }
        m_cv.notify_one();
        return {std::move(future)};
    }

    /**
     * @brief Execute a single queued task synchronously.
     * Removes one task from the queue and executes it on the calling thread.
     */
    void ProcessTask() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
    {
        std::packaged_task<void()> task;
        {
            LOCK(m_mutex);
            if (m_work_queue.empty()) return;

            // Pop the task
            task = std::move(m_work_queue.front());