chriskohlhoff / asio

Asio C++ Library
http://think-async.com/Asio
4.85k stars 1.2k forks source link

fix join() deadlock in thread_pool with no internal threads #1230

Closed jivancic closed 1 year ago

jivancic commented 1 year ago

I believe I've ran into an edge case with thread_pool. I'm trying to create a thread pool which doesn't spawn worker threads, instead all the workers attach() to it:

#include <boost/asio/thread_pool.hpp>
#include <cstddef>
#include <thread>
#include <vector>

struct my_thread_pool : public boost::asio::thread_pool
{
  explicit my_thread_pool(size_t thread_count) :
    boost::asio::thread_pool(0)  // don't spawn threads on your own
  {
    // instead use these std::threads
    for (size_t x = 0; x < thread_count; ++x)
    {
      threads_.emplace_back(
        [this]()
        {
        // ... some thread init code, e.g. change thread priority ...
        boost::asio::thread_pool::attach();
      });
    }
  }

  void join()
  {
    boost::asio::thread_pool::join();
    for (auto& thread : threads_)
      thread.join();
    threads_.clear();
  }

  ~my_thread_pool()
  {
    stop();
    join();
  }

private:
  std::vector<std::thread> threads_;
};

However, this code will deadlock

int main()
{
  my_thread_pool pool(1);
  pool.join();
}

The reason is that the boost::asio::thread_pool::join() call is a no-op in case there are 0 internal threads, so attached threads never leave the attach(). This contradicts the documentation for attach():

Blocks the calling thread until the pool is stopped or joined and has no outstanding work.

The fix seems simple enough.

EDIT: I got my code working by calling boost::asio::thread_pool::wait() instead of boost::asio::thread_pool::join(); this thing does what I wanted it to - It's not explicitly stated in the documentation for attach(), but it will also unblock it, unconditionally.

vinipsmaker commented 1 year ago

Landed in master: https://github.com/chriskohlhoff/asio/commit/7781d2cad08cb58df5fe72af9446a3e042ea0e84

Please close the PR.