From 294cc745352bd26e662d9bb6e1e0e81b809a9161 Mon Sep 17 00:00:00 2001 From: Chunting Gu Date: Sun, 9 Sep 2018 11:45:23 +0800 Subject: [PATCH] Fix dead loop of HttpClient on Linux; Allow to call Request() multiple times on the same HttpClient object. --- example/rest_book_client/main.cc | 33 +++++++++------ webcc/http_client.cc | 73 ++++++++++++++------------------ webcc/http_client.h | 4 +- 3 files changed, 54 insertions(+), 56 deletions(-) diff --git a/example/rest_book_client/main.cc b/example/rest_book_client/main.cc index 72ebe9e..b85f578 100644 --- a/example/rest_book_client/main.cc +++ b/example/rest_book_client/main.cc @@ -215,28 +215,33 @@ int main(int argc, char* argv[]) { PrintSeparator(); std::list books; - list_client.ListBooks(&books); - PrintBookList(books); + if (list_client.ListBooks(&books)) { + PrintBookList(books); + } PrintSeparator(); std::string id; - if (!list_client.CreateBook("1984", 12.3, &id)) { - return 1; + if (list_client.CreateBook("1984", 12.3, &id)) { + std::cout << "Book ID: " << id << std::endl; + } else { + id = "1"; + std::cout << "Book ID: " << id << " (faked)"<< std::endl; } - std::cout << "Book ID: " << id << std::endl; PrintSeparator(); books.clear(); - list_client.ListBooks(&books); - PrintBookList(books); + if (list_client.ListBooks(&books)) { + PrintBookList(books); + } PrintSeparator(); Book book; - detail_client.GetBook(id, &book); - PrintBook(book); + if (detail_client.GetBook(id, &book)) { + PrintBook(book); + } PrintSeparator(); @@ -244,8 +249,9 @@ int main(int argc, char* argv[]) { PrintSeparator(); - detail_client.GetBook(id, &book); - PrintBook(book); + if (detail_client.GetBook(id, &book)) { + PrintBook(book); + } PrintSeparator(); @@ -254,8 +260,9 @@ int main(int argc, char* argv[]) { PrintSeparator(); books.clear(); - list_client.ListBooks(&books); - PrintBookList(books); + if (list_client.ListBooks(&books)) { + PrintBookList(books); + } return 0; } diff --git a/webcc/http_client.cc b/webcc/http_client.cc index 1d92e13..c6f5f4b 100644 --- a/webcc/http_client.cc +++ b/webcc/http_client.cc @@ -34,6 +34,8 @@ void HttpClient::SetTimeout(int seconds) { } bool HttpClient::Request(const HttpRequest& request) { + io_context_.restart(); + response_.reset(new HttpResponse()); response_parser_.reset(new HttpResponseParser(response_.get())); @@ -72,22 +74,8 @@ Error HttpClient::Connect(const HttpRequest& request) { LOG_VERB("Connect to server..."); - ec = boost::asio::error::would_block; - - // ConnectHandler: void (boost::system::error_code, tcp::endpoint) - // Using |boost::lambda::var()| is identical to: - // boost::asio::async_connect( - // socket_, endpoints, - // [this, &ec](boost::system::error_code inner_ec, tcp::endpoint) { - // ec = inner_ec; - // }); - boost::asio::async_connect(socket_, endpoints, - boost::lambda::var(ec) = boost::lambda::_1); - - // Block until the asynchronous operation has completed. - do { - io_context_.run_one(); - } while (ec == boost::asio::error::would_block); + // Use sync API directly since we don't need timeout control. + boost::asio::connect(socket_, endpoints, ec); // Determine whether a connection was successfully established. if (ec) { @@ -98,11 +86,6 @@ Error HttpClient::Connect(const HttpRequest& request) { LOG_VERB("Socket connected."); - // ISSUE: |async_connect| reports success on failure. - // See the following bugs: - // - https://svn.boost.org/trac10/ticket/8795 - // - https://svn.boost.org/trac10/ticket/8995 - return kNoError; } @@ -114,16 +97,10 @@ Error HttpClient::SendReqeust(const HttpRequest& request) { // I find that it's almost impossible to simulate a situation in the server // side to test this timeout. - boost::system::error_code ec = boost::asio::error::would_block; - - // WriteHandler: void (boost::system::error_code, std::size_t) - boost::asio::async_write(socket_, request.ToBuffers(), - boost::lambda::var(ec) = boost::lambda::_1); + boost::system::error_code ec; - // Block until the asynchronous operation has completed. - do { - io_context_.run_one(); - } while (ec == boost::asio::error::would_block); + // Use sync API directly since we don't need timeout control. + boost::asio::write(socket_, request.ToBuffers(), ec); if (ec) { LOG_ERRO("Socket write error (%s).", ec.message().c_str()); @@ -140,7 +117,7 @@ Error HttpClient::ReadResponse() { LOG_VERB("Read response (timeout: %ds)...", timeout_seconds_); deadline_.expires_from_now(boost::posix_time::seconds(timeout_seconds_)); - AsyncWaitDeadline(); + DoWaitDeadline(); Error error = kNoError; DoReadResponse(&error); @@ -208,23 +185,37 @@ void HttpClient::DoReadResponse(Error* error) { } while (ec == boost::asio::error::would_block); } -void HttpClient::AsyncWaitDeadline() { - deadline_.async_wait(std::bind(&HttpClient::DeadlineHandler, this, +void HttpClient::DoWaitDeadline() { + deadline_.async_wait(std::bind(&HttpClient::OnDeadline, this, std::placeholders::_1)); } -void HttpClient::DeadlineHandler(boost::system::error_code ec) { - LOG_VERB("Deadline handler."); - - if (ec == boost::asio::error::operation_aborted) { - LOG_VERB("Deadline timer canceled."); +void HttpClient::OnDeadline(boost::system::error_code ec) { + if (stopped_) { return; } - LOG_WARN("HTTP client timed out."); - timed_out_ = true; + LOG_VERB("OnDeadline."); + + // NOTE: Can't check this: + // if (ec == boost::asio::error::operation_aborted) { + // LOG_VERB("Deadline timer canceled."); + // return; + // } + + if (deadline_.expires_at() <= + boost::asio::deadline_timer::traits_type::now()) { + // The deadline has passed. + // The socket is closed so that any outstanding asynchronous operations + // are canceled. + LOG_WARN("HTTP client timed out."); + timed_out_ = true; + Stop(); + return; + } - Stop(); + // Put the actor back to sleep. + DoWaitDeadline(); } void HttpClient::Stop() { diff --git a/webcc/http_client.h b/webcc/http_client.h index cc0de64..22303ae 100644 --- a/webcc/http_client.h +++ b/webcc/http_client.h @@ -48,8 +48,8 @@ class HttpClient { void DoReadResponse(Error* error); - void AsyncWaitDeadline(); - void DeadlineHandler(boost::system::error_code ec); + void DoWaitDeadline(); + void OnDeadline(boost::system::error_code ec); void Stop();