From f036077547b86438d35f20cb63c4c7f6153cc1be Mon Sep 17 00:00:00 2001 From: Richard Hodges Date: Tue, 7 Jul 2020 12:31:45 +0200 Subject: [PATCH] Fix race in http-crawl --- CHANGELOG.md | 4 ++ example/http/client/crawl/http_crawl.cpp | 62 +++++++++++------------- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16211ef5..2ac3ed44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +* Fix race in http-crawl example. + +-------------------------------------------------------------------------------- + Version 298: * Support BOOST_ASIO_NO_TS_EXECUTORS. diff --git a/example/http/client/crawl/http_crawl.cpp b/example/http/client/crawl/http_crawl.cpp index 5a621d14..da925207 100644 --- a/example/http/client/crawl/http_crawl.cpp +++ b/example/http/client/crawl/http_crawl.cpp @@ -46,7 +46,6 @@ using tcp = net::ip::tcp; // from // This structure aggregates statistics on all the sites class crawl_report { - net::io_context& ioc_; net::strand< net::io_context::executor_type> strand_; std::atomic index_; @@ -55,8 +54,7 @@ class crawl_report public: crawl_report(net::io_context& ioc) - : ioc_(ioc) - , strand_(ioc_.get_executor()) + : strand_(ioc.get_executor()) , index_(0) , hosts_(urls_large_data()) { @@ -150,6 +148,7 @@ class worker : public std::enable_shared_from_this }; crawl_report& report_; + net::strand ex_; tcp::resolver resolver_; beast::tcp_stream stream_; beast::flat_buffer buffer_; // (Must persist between reads) @@ -164,8 +163,9 @@ public: crawl_report& report, net::io_context& ioc) : report_(report) - , resolver_(net::make_strand(ioc)) - , stream_(net::make_strand(ioc)) + , ex_(net::make_strand(ioc.get_executor())) + , resolver_(ex_) + , stream_(ex_) { // Set up the common fields of the request req_.version(11); @@ -347,21 +347,14 @@ int main(int argc, char* argv[]) std::cerr << "Usage: http-crawl \n" << "Example:\n" << - " http-crawl 100 1\n"; + " http-crawl 100\n"; return EXIT_FAILURE; } auto const threads = std::max(1, std::atoi(argv[1])); - // The io_context is required for all I/O + // The io_context is used to aggregate the statistics net::io_context ioc; - // Building a tracked executor ensures that the underlying context's - // run() function will not return until the tracked executor is destroyed - net::any_io_executor work = - net::require( - ioc.get_executor(), - net::execution::outstanding_work.tracked); - // The report holds the aggregated statistics crawl_report report{ioc}; @@ -371,17 +364,26 @@ int main(int argc, char* argv[]) std::vector workers; workers.reserve(threads + 1); for(int i = 0; i < threads; ++i) + { + // Each worker will eventually add some data to the aggregated + // report. Outstanding work is tracked in each worker to + // represent the forthcoming delivery of this data by that + // worker. + auto reporting_work = net::require( + ioc.get_executor(), + net::execution::outstanding_work.tracked); + workers.emplace_back( - [&report] - { - // We use a separate io_context for each worker because - // the asio resolver simulates asynchronous operation using - // a dedicated worker thread per io_context, and we want to - // do a lot of name resolutions in parallel. - net::io_context ioc{1}; - std::make_shared(report, ioc)->run(); - ioc.run(); - }); + [&report, reporting_work] { + // We use a separate io_context for each worker because + // the asio resolver simulates asynchronous operation using + // a dedicated worker thread per io_context, and we want to + // do a lot of name resolutions in parallel. + net::io_context ioc; + std::make_shared(report, ioc)->run(); + ioc.run(); + }); + } // Add another thread to run the main io_context which // is used to aggregate the statistics @@ -393,17 +395,7 @@ int main(int argc, char* argv[]) // Now block until all threads exit for(std::size_t i = 0; i < workers.size(); ++i) - { - auto& thread = workers[i]; - - // If this is the last thread, reset the - // work object so that it can return from run. - if(i == workers.size() - 1) - work = {}; - - // Wait for the thread to exit - thread.join(); - } + workers[i].join(); std::cout << "Elapsed time: " << chrono::duration_cast(t.elapsed()).count() << " seconds\n";