threadpool: add ranged Submit overload #34576

pull andrewtoth wants to merge 1 commits into bitcoin:master from andrewtoth:threadpool_submitmany changing 2 files +107 −3
  1. andrewtoth commented at 5:39 pm on February 12, 2026: contributor

    The current ThreadPool::Submit is not very efficient when we have a use case where we need to submit multiple tasks immediately. The Submit method must take the lock for each task, and notifies only a single worker thread. This will cause lock contention with the awakened worker thread trying to take the lock and the caller trying to submit the next task.

    Introduce a Submit overload, which takes the lock once and submits a range of tasks, then notifies all worker threads after the lock is released.

    This is needed for #31132 to be able to use ThreadPool.

  2. DrahtBot commented at 5:40 pm on February 12, 2026: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK l0rinc, sedited, willcl-ark, rkrux
    Stale ACK sipa, furszy

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #31132 (validation: fetch block inputs on parallel threads 3x faster IBD by andrewtoth)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  3. furszy commented at 8:16 pm on February 12, 2026: member
    Concept ACK
  4. in src/util/threadpool.h:189 in af1196686f
    184+    template <class F> [[nodiscard]] EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
    185+    auto SubmitMany(std::vector<F>&& fns)
    186+    {
    187+        using Result = std::invoke_result_t<F&>;
    188+        std::vector<std::future<Result>> futures;
    189+        if (fns.empty()) return futures;
    


    willcl-ark commented at 8:13 pm on February 17, 2026:

    This appears to behave slightly differently to Submit, which will throw when an empty fn is passed into an interrupted pool. Here, if an empty vector of fn is passed in, we short-circuit the throw and return an empty vector.

    Not sure if this matters, or if we want to prefer a throw or think returning an empty output to an empty input is more sensible.


    andrewtoth commented at 6:42 pm on February 19, 2026:
    Removed this check. I don’t think anyone will be submitting empty vectors anyways.
  5. andrewtoth force-pushed on Feb 19, 2026
  6. in src/util/threadpool.h:204 in c985542d8c
    199+     *
    200+     * @warning Ignoring the returned futures requires guarding tasks against
    201+     *          uncaught exceptions, as they would otherwise be silently discarded.
    202+     */
    203+    template <class F>
    204+    [[nodiscard]] util::Expected<std::vector<std::future<std::invoke_result_t<F>>>, SubmitError> SubmitMany(std::vector<F>&& fns) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
    


    furszy commented at 7:02 pm on February 19, 2026:
    Why the “Many” suffix? the vector argument makes that intent clear. We could overload Submit instead.

    furszy commented at 7:11 pm on February 19, 2026:

    General question to think about (no need to answer here):

    Is there any use-case for submitting callables with different return types? Because if there is, then we are forced to use a variadic-based approach.


    furszy commented at 8:32 pm on February 19, 2026:

    Also, some thoughts about this function:

    1. It should accept more types, not only a vector of callables. We don’t know what the caller is using std::array/std::vector/std::list (or any other container with a range).
    2. It should reject L-values. The caller must std::move the container so they are not copying it.

    A patch that allows both points:

     0diff --git a/src/util/threadpool.h b/src/util/threadpool.h
     1--- a/src/util/threadpool.h
     2+++ b/src/util/threadpool.h
     3@@ -164,7 +164,10 @@
     4      *          uncaught exceptions, as they would otherwise be silently discarded.
     5      */
     6     template <class F>
     7-    [[nodiscard]] util::Expected<std::future<std::invoke_result_t<F>>, SubmitError> Submit(F&& fn) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
     8+    using Future = std::future<std::invoke_result_t<F>>;
     9+
    10+    template <class F>
    11+    [[nodiscard]] util::Expected<Future<F>, SubmitError> Submit(F&& fn) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
    12     {
    13         std::packaged_task<std::invoke_result_t<F>()> task{std::forward<F>(fn)};
    14         auto future{task.get_future()};
    15@@ -200,11 +203,13 @@
    16      * [@warning](/bitcoin-bitcoin/contributor/warning/) Ignoring the returned futures requires guarding tasks against
    17      *          uncaught exceptions, as they would otherwise be silently discarded.
    18      */
    19-    template <class F>
    20-    [[nodiscard]] util::Expected<std::vector<std::future<std::invoke_result_t<F>>>, SubmitError> SubmitMany(std::vector<F>&& fns) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
    21+    template <class F> using RangeValue = Future<std::ranges::range_value_t<F>>;
    22+
    23+    template <std::ranges::sized_range R>
    24+    requires (!std::is_lvalue_reference_v<R>) // Reject l-values; caller must explicitly std::move the range
    25+    [[nodiscard]] util::Expected<std::vector<RangeValue<R>>, SubmitError> SubmitMany(R&& fns) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
    26     {
    27-        using Result = std::invoke_result_t<F&>;
    28-        std::vector<std::future<Result>> futures;
    29+        std::vector<RangeValue<R>> futures;
    30         futures.reserve(fns.size());
    31 
    32         {
    

    It would also be nice to expand submit_many_tasks_complete_successfully to use a different container, just to exercise the change. Could use std::array or std::list.


    andrewtoth commented at 3:34 am on February 20, 2026:
    Can we add a new overload if the need arises?
  7. in src/util/threadpool.h:206 in c985542d8c
    201+     *          uncaught exceptions, as they would otherwise be silently discarded.
    202+     */
    203+    template <class F>
    204+    [[nodiscard]] util::Expected<std::vector<std::future<std::invoke_result_t<F>>>, SubmitError> SubmitMany(std::vector<F>&& fns) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
    205+    {
    206+        using Result = std::invoke_result_t<F&>;
    


    furszy commented at 7:39 pm on February 19, 2026:

    There is a mismatch between the function return type and this result type. I don’t think no one sane would do something like:

    0struct VerySaneCallable {
    1    int operator()() &  { return 1; } 
    2    double operator()() && { return 2.0; }
    3};
    

    But it would be nice to align them.

  8. andrewtoth force-pushed on Feb 20, 2026
  9. andrewtoth force-pushed on Feb 20, 2026
  10. andrewtoth commented at 3:33 am on February 20, 2026: contributor
    Thanks @furszy and @willcl-ark. Taken your suggestions and rebased.
  11. DrahtBot added the label CI failed on Feb 20, 2026
  12. andrewtoth force-pushed on Feb 20, 2026
  13. furszy commented at 4:00 am on February 20, 2026: member
    ACK 3295552c0a895fa650c0e0c67600c384fdbcde50
  14. DrahtBot removed the label CI failed on Feb 20, 2026
  15. in src/test/threadpool_tests.cpp:122 in 3295552c0a
    118@@ -110,6 +119,42 @@ BOOST_AUTO_TEST_CASE(submit_tasks_complete_successfully)
    119     BOOST_CHECK_EQUAL(threadPool.WorkQueueSize(), 0);
    120 }
    121 
    122+// Test 10, submit range of tasks in one lock acquisition
    


    sipa commented at 5:49 pm on February 20, 2026:
    Nit: put test 10 between test 9 and 11?

    maflcko commented at 8:13 pm on February 26, 2026:
    Could also call them 1a and 1b :sweat_smile:

    andrewtoth commented at 0:20 am on February 28, 2026:
    Moved to 14.
  16. sipa commented at 6:06 pm on February 20, 2026: member
    Code review ACK 3295552c0a895fa650c0e0c67600c384fdbcde50
  17. andrewtoth renamed this:
    threadpool: add SubmitMany
    threadpool: add ranged Submit overload
    on Feb 22, 2026
  18. DrahtBot added the label Needs rebase on Feb 26, 2026
  19. andrewtoth force-pushed on Feb 28, 2026
  20. andrewtoth commented at 0:19 am on February 28, 2026: contributor

    Rebased due to #34562. Addressed nit (https://github.com/bitcoin/bitcoin/pull/34576#discussion_r2834406059). Reworded commit message. Only test changes since last push.

    git range-diff 739f75c0980ef30f676183323f4cb41fb9272b71..3295552c0a895fa650c0e0c67600c384fdbcde50 9cad97f6cdf1bd660732cd10e844a6a7e0771ea0..43c5c77fcb6f68fee5249c3b8666cd86dd1c9038

  21. DrahtBot added the label CI failed on Feb 28, 2026
  22. DrahtBot removed the label Needs rebase on Feb 28, 2026
  23. in src/test/threadpool_tests.cpp:323 in 43c5c77fcb
    319@@ -311,7 +320,7 @@ BOOST_AUTO_TEST_CASE(congestion_more_workers_than_cores)
    320     BOOST_CHECK_EQUAL(counter.load(), num_tasks);
    321 }
    322 
    323-// Test 10, Interrupt() prevents further submissions
    324+// Test 11, Interrupt() prevents further submissions
    


    furszy commented at 3:12 pm on February 28, 2026:
    should keep it at 10.

    andrewtoth commented at 3:52 pm on February 28, 2026:
    Oops. Done.
  24. andrewtoth force-pushed on Feb 28, 2026
  25. furszy commented at 4:00 pm on February 28, 2026: member
    ACK fdddaccc8c9cbd241ad44100e68d49e7bebd0bc4
  26. DrahtBot requested review from sipa on Feb 28, 2026
  27. DrahtBot removed the label CI failed on Feb 28, 2026
  28. in src/test/threadpool_tests.cpp:489 in fdddaccc8c
    484+
    485+    auto squares_sum{0};
    486+    for (auto& future : futures) squares_sum += future.get();
    487+
    488+    const auto expected_sum{((num_tasks * 2) * (num_tasks + 1)) / 2}; // 2x Gauss sum.
    489+    const auto expected_squares_sum{((num_tasks * 2) * (num_tasks + 1) * ((num_tasks * 2) + 1)) / 6};
    


    rkrux commented at 1:49 pm on March 5, 2026:

    Nit: Reordering might help in elucidating the modified formula, I got confused initially and then the comment helped.

    0-    const auto expected_sum{((num_tasks * 2) * (num_tasks + 1)) / 2}; // 2x Gauss sum.
    1-    const auto expected_squares_sum{((num_tasks * 2) * (num_tasks + 1) * ((num_tasks * 2) + 1)) / 6};
    2+    const auto expected_sum{2 * ((num_tasks * (num_tasks + 1)) / 2)}; // 2x Gauss sum.
    3+    const auto expected_squares_sum{2 * ((num_tasks * (num_tasks + 1) * ((num_tasks * 2) + 1)) / 6)}; // 2x sum of squares
    

    andrewtoth commented at 3:10 pm on March 7, 2026:
    Done, and moved the comment above the line so the comment is read first.
  29. in src/test/threadpool_tests.cpp:466 in fdddaccc8c
    461+{
    462+    constexpr int32_t num_tasks{50};
    463+
    464+    ThreadPool threadPool{POOL_NAME};
    465+    threadPool.Start(NUM_WORKERS_DEFAULT);
    466+    std::atomic_int32_t counter{0};
    


    rkrux commented at 1:51 pm on March 5, 2026:

    This seems less like a counter and more like the sum.

     0diff --git a/src/test/threadpool_tests.cpp b/src/test/threadpool_tests.cpp
     1index e04f2c6e6b..c75c6dd1b8 100644
     2--- a/src/test/threadpool_tests.cpp
     3+++ b/src/test/threadpool_tests.cpp
     4@@ -463,9 +463,9 @@ BOOST_AUTO_TEST_CASE(submit_range_of_tasks_complete_successfully)
     5 
     6     ThreadPool threadPool{POOL_NAME};
     7     threadPool.Start(NUM_WORKERS_DEFAULT);
     8-    std::atomic_int32_t counter{0};
     9-    const auto square{[&counter](int32_t i) {
    10-        counter.fetch_add(i, std::memory_order_relaxed);
    11+    std::atomic_int32_t sum{0};
    12+    const auto square{[&sum](int32_t i) {
    13+        sum.fetch_add(i, std::memory_order_relaxed);
    14         return i * i;
    15     }};
    16 
    17@@ -485,9 +485,9 @@ BOOST_AUTO_TEST_CASE(submit_range_of_tasks_complete_successfully)
    18     auto squares_sum{0};
    19     for (auto& future : futures) squares_sum += future.get();
    20 
    21-    BOOST_CHECK_EQUAL(counter, expected_sum);
    22+    BOOST_CHECK_EQUAL(sum, expected_sum);
    23     BOOST_CHECK_EQUAL(squares_sum, expected_squares_sum);
    24     BOOST_CHECK_EQUAL(threadPool.WorkQueueSize(), 0);
    25 }
    
  30. rkrux commented at 1:59 pm on March 5, 2026: contributor
    Couple points in the test.
  31. andrewtoth force-pushed on Mar 7, 2026
  32. andrewtoth commented at 3:12 pm on March 7, 2026: contributor

    Thanks for the review @rkrux. I took your suggestions. Test only changes style changes.

    git diff fdddaccc8c9cbd241ad44100e68d49e7bebd0bc4..2852489f4878d3de0e72de77367fae845543ec4c

  33. furszy commented at 3:14 pm on March 7, 2026: member
    reACK 2852489
  34. rkrux commented at 2:10 pm on March 9, 2026: contributor

    lgtm ACK 2852489f4878d3de0e72de77367fae845543ec4c

    Thanks for accepting the suggestions.

  35. sedited approved
  36. sedited commented at 9:22 pm on March 9, 2026: contributor

    ACK 2852489f4878d3de0e72de77367fae845543ec4c

    Can you fix the include order and some of the formatting issues as reported by clang-format-diff?

    I also fuzzed this a bit with the following patch, but quickly stoppped finding new coverage:

     0diff --git a/src/test/fuzz/threadpool.cpp b/src/test/fuzz/threadpool.cpp
     1index a5b01db138..59e2f1b7a8 100644
     2--- a/src/test/fuzz/threadpool.cpp
     3+++ b/src/test/fuzz/threadpool.cpp
     4@@ -72 +72 @@ FUZZ_TARGET(threadpool, .init = setup_threadpool_test) EXCLUSIVE_LOCKS_REQUIRED(
     5-    const uint32_t num_tasks = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, 1024);
     6+    uint32_t num_tasks = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, 1024);
     7@@ -85,0 +86,25 @@ FUZZ_TARGET(threadpool, .init = setup_threadpool_test) EXCLUSIVE_LOCKS_REQUIRED(
     8+        const bool submit_batch = fuzzed_data_provider.ConsumeBool();
     9+
    10+        if (submit_batch) {
    11+            const uint32_t batch_size = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, 100);
    12+            i += batch_size;
    13+            if (will_throw) {
    14+                expected_fail_tasks += batch_size;
    15+                auto batch_futures = *Assert(g_pool.Submit(std::vector<ThrowTask>(batch_size)));
    16+                for (auto& batch_future : batch_futures) {
    17+                    futures.emplace(std::move(batch_future));
    18+                }
    19+            } else {
    20+                std::vector<CounterTask> tasks;
    21+                tasks.reserve(batch_size);
    22+                for (uint32_t j = 0; j < batch_size; ++j) {
    23+                    expected_task_counter++;
    24+                    tasks.emplace_back(task_counter);
    25+                }
    26+                auto batch_futures = *Assert(g_pool.Submit(std::move(tasks)));
    27+                for (auto& batch_future : batch_futures) {
    28+                    futures.emplace(std::move(batch_future));
    29+                }
    30+            }
    31+            continue;
    32+        }
    
  37. andrewtoth force-pushed on Mar 10, 2026
  38. andrewtoth-exo commented at 1:00 am on March 10, 2026: none

    Thanks for the review and for fuzzing @sedited. I pushed the formatting changes.

    git diff 2852489f4878d3de0e72de77367fae845543ec4c..e62bfc241e90b60424365924f676ea85755ae935

  39. in src/test/threadpool_tests.cpp:487 in e62bfc241e
    482+    BOOST_CHECK_EQUAL(futures.size(), static_cast<size_t>(num_tasks));
    483+    std::ranges::move(*Assert(threadPool.Submit(std::move(vector_tasks))), std::back_inserter(futures));
    484+    BOOST_CHECK_EQUAL(futures.size(), static_cast<size_t>(num_tasks * 2));
    485+
    486+    auto squares_sum{0};
    487+    for (auto& future : futures)
    


    sedited commented at 7:39 am on March 10, 2026:
    This needs braces now per the dev notes. It’s a bit annoying that we can’t just apply clang-format blindly for some cases.

    andrewtoth commented at 1:28 pm on March 10, 2026:
    Do you need me to fix this? It is just a style nit. We have 3 ACKs right now.

    sedited commented at 3:48 pm on March 10, 2026:
    I think this is the one style nit that is probably worth a push, and I think it wouldn’t take a lot of time out of people’s day to re-approve.

    andrewtoth commented at 8:23 pm on March 10, 2026:
    Done.
  40. sedited approved
  41. sedited commented at 7:40 am on March 10, 2026: contributor
    Re-ACK e62bfc241e90b60424365924f676ea85755ae935
  42. DrahtBot requested review from rkrux on Mar 10, 2026
  43. DrahtBot requested review from furszy on Mar 10, 2026
  44. rkrux approved
  45. rkrux commented at 8:35 am on March 10, 2026: contributor
  46. furszy commented at 12:49 pm on March 10, 2026: member
    utACK e62bfc241e90b60424365924f676ea85755ae935
  47. fanquake added this to the milestone 32.0 on Mar 10, 2026
  48. in src/util/threadpool.h:227 in e62bfc241e
    222+
    223+        {
    224+            LOCK(m_mutex);
    225+            if (m_workers.empty()) return util::Unexpected{SubmitError::Inactive};
    226+            if (m_interrupt) return util::Unexpected{SubmitError::Interrupted};
    227+            for (auto& fn : fns) {
    


    l0rinc commented at 2:24 pm on March 10, 2026:

    e62bfc2 threadpool: add ranged Submit overload:

    What should the intended behavior be for an empty fns range? It might be worth making that explicit and short-circuiting the locking, and maybe covering it with a small test.


    l0rinc commented at 2:39 pm on March 10, 2026:

    e62bfc2 threadpool: add ranged Submit overload:

    auto& looks more restrictive than necessary here - auto&& seems like the more natural match for a forwarding/range-based path like this.


    andrewtoth commented at 4:45 pm on March 10, 2026:
    I initially did short-circuit it, but it was brought up that it is a behavior change from non-ranged Submit #34576 (review). I think best to keep behavior identical, so we throw even if fns is empty.
  49. in src/util/threadpool.h:211 in e62bfc241e
    206+     * the queue lock is only taken once internally and all worker threads are
    207+     * notified. For single tasks, Submit() is preferred since only one worker
    208+     * thread is notified.
    209+     *
    210+     * Thread-safe: Can be called from any thread, including within submitted
    211+     * callables.
    


    l0rinc commented at 2:25 pm on March 10, 2026:

    e62bfc2 threadpool: add ranged Submit overload:

    nit:

    0     * Thread-safe: Can be called from any thread, including within submitted callables.
    
  50. in src/util/threadpool.h:183 in e62bfc241e
    177@@ -171,7 +178,7 @@ class ThreadPool
    178      *          uncaught exceptions, as they would otherwise be silently discarded.
    179      */
    180     template <class F>
    181-    [[nodiscard]] util::Expected<std::future<std::invoke_result_t<F>>, SubmitError> Submit(F&& fn) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
    182+    [[nodiscard]] util::Expected<Future<F>, SubmitError> Submit(F&& fn) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
    183     {
    184         std::packaged_task<std::invoke_result_t<F>()> task{std::forward<F>(fn)};
    


    l0rinc commented at 2:38 pm on March 10, 2026:

    e62bfc2 threadpool: add ranged Submit overload:

    Nit: a bit unrelated, but for consistency, this could also be extracted to a

    0template <class F>
    1using PackagedTask = std::packaged_task<std::invoke_result_t<F>()>;
    
  51. in src/util/threadpool.h:164 in e62bfc241e
    156@@ -156,6 +157,12 @@ class ThreadPool
    157         Interrupted,
    158     };
    159 
    160+    template <class F>
    161+    using Future = std::future<std::invoke_result_t<F>>;
    162+
    163+    template <class R>
    164+    using RangeValueFuture = Future<std::ranges::range_value_t<R>>;
    


    l0rinc commented at 2:59 pm on March 10, 2026:

    e62bfc2 threadpool: add ranged Submit overload:

    My understanding is that range_reference_t<R> is more correct here, since the callable is taken from the range by reference in the loop body. I could reproduce a mismatch with a simple lvalue-only task:

    0struct LvalueOnlyTask { [[maybe_unused]] int operator()() & { return 0; } };
    1std::vector<LvalueOnlyTask> tasks(1);
    2[[maybe_unused]] auto futures{std::move(*Assert(threadPool.Submit(std::move(tasks))))};
    3static_assert(std::is_same_v<decltype(futures), std::vector<std::future<int>>>);
    
  52. in src/util/threadpool.h:228 in e62bfc241e
    223+        {
    224+            LOCK(m_mutex);
    225+            if (m_workers.empty()) return util::Unexpected{SubmitError::Inactive};
    226+            if (m_interrupt) return util::Unexpected{SubmitError::Interrupted};
    227+            for (auto& fn : fns) {
    228+                std::packaged_task task{std::move(fn)};
    


    l0rinc commented at 3:26 pm on March 10, 2026:

    e62bfc2 threadpool: add ranged Submit overload:

    If we add a PackagedTask above, we could make this simpler and more specific - the complete patch would be:

     0diff --git a/src/util/threadpool.h b/src/util/threadpool.h
     1--- a/src/util/threadpool.h	(revision a5a801e68132ffb46a1f718862e39f8155b426f3)
     2+++ b/src/util/threadpool.h	(date 1773157887841)
     3@@ -161,7 +161,10 @@
     4     using Future = std::future<std::invoke_result_t<F>>;
     5 
     6     template <class R>
     7-    using RangeValueFuture = Future<std::ranges::range_value_t<R>>;
     8+    using RangeFuture = Future<std::ranges::range_reference_t<R>>;
     9+
    10+    template <class F>
    11+    using PackagedTask = std::packaged_task<std::invoke_result_t<F>()>;
    12 
    13     /**
    14      * [@brief](/bitcoin-bitcoin/contributor/brief/) Enqueues a new task for asynchronous execution.
    15@@ -180,7 +183,7 @@
    16     template <class F>
    17     [[nodiscard]] util::Expected<Future<F>, SubmitError> Submit(F&& fn) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
    18     {
    19-        std::packaged_task<std::invoke_result_t<F>()> task{std::forward<F>(fn)};
    20+        PackagedTask<F> task{std::forward<F>(fn)};
    21         auto future{task.get_future()};
    22         {
    23             LOCK(m_mutex);
    24@@ -215,17 +218,17 @@
    25      */
    26     template <std::ranges::sized_range R>
    27         requires(!std::is_lvalue_reference_v<R>)
    28-    [[nodiscard]] util::Expected<std::vector<RangeValueFuture<R>>, SubmitError> Submit(R&& fns) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
    29+    [[nodiscard]] util::Expected<std::vector<RangeFuture<R>>, SubmitError> Submit(R&& fns) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
    30     {
    31-        std::vector<RangeValueFuture<R>> futures;
    32+        std::vector<RangeFuture<R>> futures;
    33         futures.reserve(std::ranges::size(fns));
    34 
    35         {
    36             LOCK(m_mutex);
    37             if (m_workers.empty()) return util::Unexpected{SubmitError::Inactive};
    38             if (m_interrupt) return util::Unexpected{SubmitError::Interrupted};
    39-            for (auto& fn : fns) {
    40-                std::packaged_task task{std::move(fn)};
    41+            for (auto&& fn : fns) {
    42+                PackagedTask<std::ranges::range_reference_t<R>> task{std::move(fn)};
    43                 futures.emplace_back(task.get_future());
    44                 m_work_queue.emplace(std::move(task));
    45             }
    

    andrewtoth commented at 8:24 pm on March 10, 2026:
    Done. Added you as a co-author.
  53. l0rinc changes_requested
  54. l0rinc commented at 4:07 pm on March 10, 2026: contributor
    The current version is probably fine, but it seems to me we can make it more accurate by using range reference type instead of the value type - and iterating with auto&& so the overload handles more callable/range shapes correctly. And maybe handle empty with a lockless short-circuit.
  55. threadpool: add ranged Submit overload
    Co-authored-by: l0rinc <pap.lorinc@gmail.com>
    79571b9181
  56. andrewtoth force-pushed on Mar 10, 2026
  57. andrewtoth commented at 8:28 pm on March 10, 2026: contributor

    Thanks for your review @l0rinc. I have taken most of your suggestions.

    • fixed style nit in test
    • changed lvalue to rvalue ref in fns loop
    • added PackagedTask alias
    • updated RangeValueFuture with range_value_t to RangeFuture with range_reference_t
    • fixed style nit in docstring

    git diff e62bfc241e90b60424365924f676ea85755ae935..79571b918130e66436b2d43489835c38bb3ae3e3

  58. l0rinc commented at 11:00 pm on March 10, 2026: contributor
    ACK 79571b918130e66436b2d43489835c38bb3ae3e3
  59. sedited approved
  60. sedited commented at 11:19 am on March 11, 2026: contributor
    Re-ACK 79571b918130e66436b2d43489835c38bb3ae3e3
  61. DrahtBot requested review from furszy on Mar 11, 2026
  62. DrahtBot requested review from rkrux on Mar 11, 2026
  63. willcl-ark approved
  64. willcl-ark commented at 11:38 am on March 11, 2026: member

    ACK 79571b918130e66436b2d43489835c38bb3ae3e3

    I like the template constraints via requires(), very neat :)

    I did not test with e.g. 31132 picked, but the code and tests here look good to me.

  65. l0rinc approved
  66. rkrux commented at 12:06 pm on March 11, 2026: contributor

    ACK 79571b9

    0git range-diff e62bfc2...79571b9
    
  67. sedited merged this on Mar 11, 2026
  68. sedited closed this on Mar 11, 2026


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-03-16 15:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me