title: Why io_context sopped in my boost asio coroutine server
body: <p>My server is based on boost spawn echo server example, and improved in <a href="https://stackoverflow.com/questions/58792447/boost-coroutine-server-crashes-when-writting-data-to-client">this thread</a>. The real server is complex and I made a simpler server to show the problem:</p>
  
  <p>The server listen on port 12345, receives 0x4000 bytes data from new connection.</p>
  
  <p>The client runs 1000 threads, connect to the server and send 0x4000 bytes data.</p>
  
  <p><strong>The Problem</strong>: When the client running, kill the client process after 1 second, via <em>Ctrl-C</em> in console, then the server's <code>io_context</code> would be stopped, server runs into infinit loop and costs 100% cpu. If this doesn't happen, repeat launch client and kill it several times, it will happen. Maybe after several times it runs out of TCP port, just wait some minutes and try again, it happens after killing the client 3~15 times on my machine.</p>
  
  <p>The <a href="https://www.boost.org/doc/libs/1_71_0/doc/html/boost_asio/reference/io_context/stopped.html" rel="nofollow noreferrer">boost document</a> says the <code>io_context.stopped()</code> is used to determine whether it's stopped</p>
  
  <blockquote>
    <p>either through an explicit call to stop(), or due to running out of work</p>
  </blockquote>
  
  <p>I never call <code>io_context.stop()</code>, and use a <code>make_work_guard(io_context)</code> to keep the <code>io_context</code> not stop, but why it still stopped?</p>
  
  <p>My Environment: Win10-64bit, boost 1.71.0</p>
  
  <p>Server code: </p>
  
  <pre><code>#include &lt;iostream&gt;
  using namespace std;
  
  #include &lt;boost/thread/thread.hpp&gt;
  #include &lt;boost/asio.hpp&gt;
  #include &lt;boost/asio/spawn.hpp&gt;
  using namespace boost;
  using namespace boost::asio;
  using namespace boost::asio::ip;
  namespace ba=boost::asio;
  
  #define SERVER_PORT 12345
  #define DATA_LEN 0x4000
  
  
  struct session : public std::enable_shared_from_this&lt;session&gt;
  {
      tcp::socket socket_;
      boost::asio::steady_timer timer_;
      boost::asio::strand&lt;boost::asio::io_context::executor_type&gt; strand_;
  
      explicit session(boost::asio::io_context&amp; io_context, tcp::socket socket)
      : socket_(std::move(socket)),
        timer_(io_context),
        strand_(io_context.get_executor())
      { }
  
      void go()
      {
          auto self(shared_from_this());
          boost::asio::spawn(strand_, [this, self](boost::asio::yield_context yield)
          {
              spawn(yield, [this, self](ba::yield_context yield) {
                  timer_.expires_from_now(10s); // 10 second
                  while (socket_.is_open()) {
                      boost::system::error_code ec;
                      timer_.async_wait(yield[ec]);
                      // timeout triggered, timer was not canceled
                      if (ba::error::operation_aborted != ec) {
                          socket_.close();
                      }
                  }
              });
  
              try
              {
                  // recv data
                  string packet;
  
                  // read data
                  boost::system::error_code ec;
  
                  ba::async_read(socket_,
                                 ba::dynamic_buffer(packet),
                                 ba::transfer_exactly(DATA_LEN),
                                 yield[ec]);
                  if(ec) {
                      throw "read_fail";
                  }
  
              }
              catch (...)
              {
                  cout &lt;&lt; "exception" &lt;&lt; endl;
              }
  
              timer_.cancel();
              socket_.close();
          });
  
      }
  };
  struct my_server {  
      my_server() { }
      ~my_server() { } 
  
      void start() {
          ba::io_context io_context;
          auto worker = ba::make_work_guard(io_context);
  
          ba::spawn(io_context, [&amp;](ba::yield_context yield)
          {
              tcp::acceptor acceptor(io_context,
              tcp::endpoint(tcp::v4(), SERVER_PORT));
  
              for (;;)
              {
                  boost::system::error_code ec;
  
                  tcp::socket socket(io_context);
                  acceptor.async_accept(socket, yield[ec]);
                  if (!ec) {
                      std::make_shared&lt;session&gt;(io_context, std::move(socket))-&gt;go();
                  } 
              }
          });
  
          // Run io_context on All CPUs
          auto thread_count = std::thread::hardware_concurrency();
          boost::thread_group tgroup;
          for (auto i = 0; i &lt; thread_count; ++i) 
              tgroup.create_thread([&amp;] {
                  for (;;) {
                      try { 
                          if (io_context.stopped()) { // &lt;- this happens after killing Client process several times
                              cout &lt;&lt; "io_context STOPPED, now server runs infinit loop with full cpu usage" &lt;&lt; endl;
                          }
                          io_context.run(); 
                      }
                      catch(const std::exception&amp; e) { 
                          MessageBox(0, "This never popup", e.what(), 0); 
                      }
                      catch(const boost::exception&amp; e) { 
                          MessageBox(0, "This never popup", boost::diagnostic_information(e).data(), 0); 
                      }
                      catch(...) { MessageBox(0, "This never popup", "", 0); }
                  }
              });
          tgroup.join_all();
      }
  };  
  
  int main() {
      my_server svr;
      svr.start();
  }
  </code></pre>
  
  <p>The Client:</p>
  
  <pre><code>#include &lt;iostream&gt;
  #include &lt;random&gt;
  #include &lt;thread&gt;
  #include &lt;boost/asio.hpp&gt;
  #include &lt;boost/thread.hpp&gt;
  using namespace std;
  
  using boost::asio::ip::tcp;
  namespace ba=boost::asio;
  
  #define SERVER "127.0.0.1"
  #define PORT "12345"
  
  int main() {
      boost::asio::io_context io_context;
  
      static string data_0x4000(0x4000, 'a');
  
      boost::thread_group tgroup;
      for (auto i = 0; i &lt; 1000; ++i) 
          tgroup.create_thread([&amp;] {
              for(;;) {
  
                  try {
                      tcp::socket s(io_context);
                      tcp::resolver resolver(io_context);
                      boost::asio::connect(s, resolver.resolve(SERVER, PORT));
  
                      ba::write(s, ba::buffer(data_0x4000));
                  } catch (std::exception e) {
                      cout &lt;&lt; " exception: " &lt;&lt; e.what() &lt;&lt; endl;
                  } catch (...) {
                      cout &lt;&lt; "unknown exception" &lt;&lt; endl;
                  }
              }
          });
  
      tgroup.join_all();
  
      return 0;
  }
  </code></pre>
  
  <p><strong>Update Workaround:</strong></p>
  
  <p>I guess problem happens with <code>io_context</code> and coroutine, so I tried to replace unnecessary <code>spawn</code> to <code>std::thread</code>, and it works, the <code>io_context</code> never stops anymore. But why the problem happens anyway?</p>
  
  <p>Replace:</p>
  
  <pre><code>ba::spawn(io_context, [&amp;](ba::yield_context yield)
  {
      tcp::acceptor acceptor(io_context,
      tcp::endpoint(tcp::v4(), SERVER_PORT));
  
      for (;;)
      {
          boost::system::error_code ec;
  
          tcp::socket socket(io_context);
          acceptor.async_accept(socket, yield[ec]);
          if (!ec) {
              std::make_shared&lt;session&gt;(io_context, std::move(socket))-&gt;go();
          } 
      }
  });
  </code></pre>
  
  <p>To:</p>
  
  <pre><code>std::thread([&amp;]()
  {
      tcp::acceptor acceptor(io_context,
      tcp::endpoint(tcp::v4(), SERVER_PORT));
  
      for (;;)
      {
          boost::system::error_code ec;
  
          tcp::socket socket(io_context);
          acceptor.accept(socket, ec);
          if (!ec) {
              std::make_shared&lt;session&gt;(io_context, std::move(socket))-&gt;go();
          } 
      }
  }).detach();
  </code></pre>