本文を読み飛ばす

Boost::asioとspawnとタイムアウト

Boost::asio での非同期通信プログラムにおいて、spawn() を使うスタイル(yield_context を使ったコルーチンで)対話処理を記述しつつ deadline_timer でタイムアウト制御してみたので備忘録。

要点は以下:

  • 通常通り boost::asio::deadline_timerasync_wait を発行し、そのハンドラーで socket や 。acceptorcancel() を呼ぶ
  • その後に、一定時間で打ち切りたい非同期処理を yield_context を使って呼び出す
  • その後に、その非同期処理が成功していたならばタイマーをキャンセルする

注意点は:

  • タイマーのハンドラーに渡されるエラーコードは、非同期処理が指定時間内に成功したならば「エラー」(boost::system::errc::operation_canceled)、指定時間内に成功しなければ「成功」になる
  • 非同期処理の成功後および(おそらく)タイムアウト=タイマーのハンドラーによるキャンセルではないエラーの検出時に、タイマーをキャンセルする

こんな感じで実装できる:

namespace asio = boost::asio;
using std::placeholders::_1;

const char *server_address = "127.0.0.1";
const u_short server_port = 9999;

class MyClient
{
public:
  MyClient()
    : io_()
    , socket_(io_)
  {}

  int run()
  {
    boost::system::error_code ec;

    asio::spawn(io_, std::bind(&MyClient::talk, this, _1));
    io_.run(ec);
    if (ec) {
      return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
  }

  void talk(asio::yield_context yield)
  {
    boost::system::error_code ec;
    auto remote_address = asio::ip::address::from_string(server_address);
    asio::ip::tcp::endpoint remote_endpoint(remote_address, server_port);
    asio::deadline_timer timer(io_);

    auto timeout_duration = boost::posix_time::seconds(4);
    timer.expires_from_now(timeout_duration);
    timer.async_wait([this](const boost::system::error_code& ec2) {
      if (!ec2) {
        std::cerr << "# Canceling socket operation.\n";
        socket_.cancel();
      }
    });

    std::cout << "Connecting to " << remote_endpoint << "..." << "\n";
    socket_.async_connect(remote_endpoint, yield[ec]);
    if (ec) {
      if (ec == boost::system::errc::operation_canceled) {
        std::cerr << "Canceled to connect; timed out. error=" << ec << "\n";
      }
      else if (ec) {
        timer.cancel();
        std::cerr << "Failed to connect. error=" << ec << "\n";
      }
      return;
    }
    std::cout << "Successfully connected.\n";
    timer.cancel();

    std::cout << "Sleeping for 3 seconds...\n";
    std::this_thread::sleep_for(std::chrono::seconds(3));

    std::cout << "Done.\n";
    socket_.close();
  }

private:
  asio::io_service io_;
  asio::ip::tcp::socket socket_;
};

int main()
{
  return MyClient().run();
}

簡単に試した限りでは動いてくれた。本当はもっと叩いて安全性などを確認したいところなのだけれど、最近、どうにも時間が無い。。今日はこれまで。