diff --git a/.travis.yml b/.travis.yml index 96efe8e73..0035abc4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +sudo: false language: cpp env: @@ -11,7 +12,7 @@ env: - LCOV_ROOT=$HOME/lcov - VALGRIND_ROOT=$HOME/valgrind-install - BOOST_ROOT=$HOME/boost_1_61_0 - - BOOST_URL='http://downloads.sourceforge.net/project/boost/boost/1.61.0/boost_1_61_0.tar.gz?use_mirror=netix' + - BOOST_URL='http://sourceforge.net/projects/boost/files/boost/1.61.0/boost_1_61_0.tar.gz' packages: &gcc5_pkgs - gcc-5 - g++-5 @@ -30,7 +31,7 @@ packages: &gcc5_pkgs matrix: include: - # GCC/Coverage/Autobahn + # GCC/Coverage/Autobahn (if master or develop branch) - compiler: gcc env: - GCC_VER=5 @@ -77,7 +78,7 @@ before_install: - scripts/install-dependencies.sh script: - - scripts/build-and-test.sh + - travis_retry scripts/build-and-test.sh after_script: - cat nohup.out || echo "nohup.out already deleted" diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ac23dea1..515988fbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +1.0.0-b23 + +* Tune websocket echo server for performance +* Add file and line number to thrown exceptions +* Better logging in async echo server +* Add copy special members +* Fix message constructor and special members +* Travis CI improvements + +-------------------------------------------------------------------------------- + 1.0.0-b22 * Fix broken Intellisense diff --git a/include/beast/core/detail/buffer_cat.hpp b/include/beast/core/detail/buffer_cat.hpp index fdf2b6104..b94d99656 100644 --- a/include/beast/core/detail/buffer_cat.hpp +++ b/include/beast/core/detail/buffer_cat.hpp @@ -253,7 +253,8 @@ private: reference dereference(C const&) const { - throw std::logic_error("invalid iterator"); + throw detail::make_exception( + "invalid iterator", __FILE__, __LINE__); } template @@ -269,7 +270,8 @@ private: void increment(C const&) { - throw std::logic_error("invalid iterator"); + throw detail::make_exception( + "invalid iterator", __FILE__, __LINE__); } template @@ -310,7 +312,8 @@ private: --iter(); return; } - throw std::logic_error("invalid iterator"); + throw detail::make_exception( + "invalid iterator", __FILE__, __LINE__); } template diff --git a/include/beast/core/detail/type_traits.hpp b/include/beast/core/detail/type_traits.hpp index 24928abdd..f50b3a3c0 100644 --- a/include/beast/core/detail/type_traits.hpp +++ b/include/beast/core/detail/type_traits.hpp @@ -10,6 +10,8 @@ #include #include +#include +#include namespace beast { namespace detail { @@ -78,6 +80,18 @@ struct repeat_tuple<0, T> using type = std::tuple<>; }; +template +Exception +make_exception(char const* reason, char const* file, int line) +{ + char const* n = file; + for(auto p = file; *p; ++p) + if(*p == '\\' || *p == '/') + n = p + 1; + return Exception{std::string(reason) + " (" + + n + ":" + std::to_string(line) + ")"}; +} + } // detail } // beast diff --git a/include/beast/core/impl/basic_streambuf.ipp b/include/beast/core/impl/basic_streambuf.ipp index c70d646bb..a3fdcef70 100644 --- a/include/beast/core/impl/basic_streambuf.ipp +++ b/include/beast/core/impl/basic_streambuf.ipp @@ -524,8 +524,8 @@ basic_streambuf::basic_streambuf( , alloc_size_(alloc_size) { if(alloc_size <= 0) - throw std::invalid_argument( - "basic_streambuf: invalid alloc_size"); + throw detail::make_exception( + "invalid alloc_size", __FILE__, __LINE__); } template diff --git a/include/beast/core/impl/buffers_adapter.ipp b/include/beast/core/impl/buffers_adapter.ipp index cf537d6e4..309830f1e 100644 --- a/include/beast/core/impl/buffers_adapter.ipp +++ b/include/beast/core/impl/buffers_adapter.ipp @@ -8,6 +8,7 @@ #ifndef BEAST_IMPL_BUFFERS_ADAPTER_IPP #define BEAST_IMPL_BUFFERS_ADAPTER_IPP +#include #include #include #include @@ -413,8 +414,8 @@ buffers_adapter::prepare(std::size_t n) -> } } if(n > 0) - throw std::length_error( - "no space in buffers_adapter"); + throw detail::make_exception( + "no space", __FILE__, __LINE__); return mutable_buffers_type{*this}; } diff --git a/include/beast/core/impl/static_streambuf.ipp b/include/beast/core/impl/static_streambuf.ipp index 3f19cd7e1..44c4e2610 100644 --- a/include/beast/core/impl/static_streambuf.ipp +++ b/include/beast/core/impl/static_streambuf.ipp @@ -8,6 +8,7 @@ #ifndef BEAST_IMPL_STATIC_STREAMBUF_IPP #define BEAST_IMPL_STATIC_STREAMBUF_IPP +#include #include #include #include @@ -295,7 +296,8 @@ static_streambuf::prepare(std::size_t n) -> mutable_buffers_type { if(n > static_cast(end_ - out_)) - throw std::length_error("no space in streambuf"); + throw detail::make_exception( + "no space in streambuf", __FILE__, __LINE__); last_ = out_ + n; return mutable_buffers_type{out_, n}; } diff --git a/include/beast/core/static_string.hpp b/include/beast/core/static_string.hpp index a3ec8919d..6a9ef1e6a 100644 --- a/include/beast/core/static_string.hpp +++ b/include/beast/core/static_string.hpp @@ -8,6 +8,7 @@ #ifndef BEAST_WEBSOCKET_STATIC_STRING_HPP #define BEAST_WEBSOCKET_STATIC_STRING_HPP +#include #include #include #include @@ -329,7 +330,8 @@ static_string:: static_string(static_string const& s) { if(s.size() > N) - throw std::length_error("static_string overflow"); + throw detail::make_exception( + "static_string overflow", __FILE__, __LINE__); n_ = s.size(); Traits::copy(&s_[0], &s.s_[0], n_ + 1); } @@ -353,7 +355,8 @@ operator=(static_string const& s) -> static_string& { if(s.size() > N) - throw std::length_error("static_string overflow"); + throw detail::make_exception( + "static_string overflow", __FILE__, __LINE__); n_ = s.size(); Traits::copy(&s_[0], &s.s_[0], n_ + 1); return *this; @@ -391,7 +394,8 @@ at(size_type pos) -> reference { if(pos >= n_) - throw std::out_of_range("static_string::at"); + throw detail::make_exception( + "invalid pos", __FILE__, __LINE__); return s_[pos]; } @@ -402,7 +406,8 @@ at(size_type pos) const -> const_reference { if(pos >= n_) - throw std::out_of_range("static_string::at"); + throw detail::make_exception( + "static_string::at", __FILE__, __LINE__); return s_[pos]; } @@ -412,7 +417,8 @@ static_string:: resize(std::size_t n) { if(n > N) - throw std::length_error("static_string overflow"); + throw detail::make_exception( + "static_string overflow", __FILE__, __LINE__); n_ = n; s_[n_] = 0; } @@ -423,7 +429,8 @@ static_string:: resize(std::size_t n, CharT c) { if(n > N) - throw std::length_error("static_string overflow"); + throw detail::make_exception( + "static_string overflow", __FILE__, __LINE__); if(n > n_) Traits::assign(&s_[n_], n - n_, c); n_ = n; @@ -462,7 +469,8 @@ assign(CharT const* s) { auto const n = Traits::length(s); if(n > N) - throw std::out_of_range("too large"); + throw detail::make_exception( + "too large", __FILE__, __LINE__); n_ = n; Traits::copy(&s_[0], s, n_ + 1); } diff --git a/include/beast/http/impl/message.ipp b/include/beast/http/impl/message.ipp index 9a818cd87..3f7c05650 100644 --- a/include/beast/http/impl/message.ipp +++ b/include/beast/http/impl/message.ipp @@ -159,6 +159,8 @@ void prepare(message& msg, Options&&... options) { + using beast::detail::make_exception; + // VFALCO TODO static_assert(is_Body::value, "Body requirements not met"); @@ -174,16 +176,16 @@ prepare(message& msg, std::forward(options)...); if(msg.fields.exists("Connection")) - throw std::invalid_argument( - "prepare called with Connection field set"); + throw make_exception( + "prepare called with Connection field set", __FILE__, __LINE__); if(msg.fields.exists("Content-Length")) - throw std::invalid_argument( - "prepare called with Content-Length field set"); + throw make_exception( + "prepare called with Content-Length field set", __FILE__, __LINE__); if(token_list{msg.fields["Transfer-Encoding"]}.exists("chunked")) - throw std::invalid_argument( - "prepare called with Transfer-Encoding: chunked set"); + throw make_exception( + "prepare called with Transfer-Encoding: chunked set", __FILE__, __LINE__); if(pi.connection_value != connection::upgrade) { @@ -254,8 +256,8 @@ prepare(message& msg, // rfc7230 6.7. if(msg.version < 11 && token_list{ msg.fields["Connection"]}.exists("upgrade")) - throw std::invalid_argument( - "invalid version for Connection: upgrade"); + throw make_exception( + "invalid version for Connection: upgrade", __FILE__, __LINE__); } } // http diff --git a/include/beast/http/message.hpp b/include/beast/http/message.hpp index ebe172384..007b48746 100644 --- a/include/beast/http/message.hpp +++ b/include/beast/http/message.hpp @@ -246,6 +246,18 @@ struct message : header /// Default constructor message() = default; + /// Move constructor + message(message&&) = default; + + /// Copy constructor + message(message const&) = default; + + /// Move assignment + message& operator=(message&&) = default; + + /// Copy assignment + message& operator=(message const&) = default; + /** Construct a message from a header. Additional arguments, if any, are forwarded to @@ -281,8 +293,9 @@ struct message : header */ template::type, base_type>::value> + , class = typename std::enable_if< + ! std::is_convertible::type, base_type>::value>::type #endif > explicit @@ -303,7 +316,7 @@ struct message : header template::type, base_type>::value> + typename std::decay::type, base_type>::value>::type #endif > message(U&& u, V&& v) diff --git a/include/beast/version.hpp b/include/beast/version.hpp index 7d02e3293..cc71abf07 100644 --- a/include/beast/version.hpp +++ b/include/beast/version.hpp @@ -16,6 +16,6 @@ // #define BEAST_VERSION 100000 -#define BEAST_VERSION_STRING "1.0.0-b22" +#define BEAST_VERSION_STRING "1.0.0-b23" #endif diff --git a/include/beast/websocket/impl/stream.ipp b/include/beast/websocket/impl/stream.ipp index bd04db45a..2e4192498 100644 --- a/include/beast/websocket/impl/stream.ipp +++ b/include/beast/websocket/impl/stream.ipp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/include/beast/websocket/option.hpp b/include/beast/websocket/option.hpp index d2864cdd2..4f009f969 100644 --- a/include/beast/websocket/option.hpp +++ b/include/beast/websocket/option.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -186,7 +187,8 @@ struct message_type message_type(opcode op) { if(op != opcode::binary && op != opcode::text) - throw std::domain_error("bad opcode"); + throw beast::detail::make_exception( + "bad opcode", __FILE__, __LINE__); value = op; } }; @@ -277,6 +279,9 @@ struct read_buffer_size read_buffer_size(std::size_t n) : value(n) { + if(n < 8) + throw beast::detail::make_exception( + "read buffer size is too small", __FILE__, __LINE__); } }; #endif @@ -356,7 +361,8 @@ struct write_buffer_size : value(n) { if(n < 8) - throw std::domain_error("write buffer size is too small"); + throw beast::detail::make_exception( + "write buffer size is too small", __FILE__, __LINE__); } }; #endif diff --git a/include/beast/zlib/detail/deflate_stream.hpp b/include/beast/zlib/detail/deflate_stream.hpp index 7b367d3e4..45dbdb98b 100644 --- a/include/beast/zlib/detail/deflate_stream.hpp +++ b/include/beast/zlib/detail/deflate_stream.hpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -893,14 +894,19 @@ doReset( if(windowBits == 8) windowBits = 9; + using beast::detail::make_exception; + if(level < 0 || level > 9) - throw std::invalid_argument{"invalid level"}; + throw make_exception( + "invalid level", __FILE__, __LINE__); if(windowBits < 8 || windowBits > 15) - throw std::invalid_argument{"invalid windowBits"}; + throw make_exception( + "invalid windowBits", __FILE__, __LINE__); if(memLevel < 1 || memLevel > MAX_MEM_LEVEL) - throw std::invalid_argument{"invalid memLevel"}; + throw make_exception( + "invalid memLevel", __FILE__, __LINE__); w_bits_ = windowBits; diff --git a/include/beast/zlib/detail/inflate_stream.hpp b/include/beast/zlib/detail/inflate_stream.hpp index c13d9428c..5b748fbe3 100644 --- a/include/beast/zlib/detail/inflate_stream.hpp +++ b/include/beast/zlib/detail/inflate_stream.hpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -232,7 +233,8 @@ inflate_stream:: doReset(int windowBits) { if(windowBits < 8 || windowBits > 15) - throw std::domain_error("windowBits out of range"); + throw beast::detail::make_exception( + "windowBits out of range", __FILE__, __LINE__); w_.reset(windowBits); bi_.flush(); @@ -706,7 +708,8 @@ doWrite(z_params& zs, Flush flush, error_code& ec) case SYNC: default: - throw std::logic_error("stream error"); + throw beast::detail::make_exception( + "stream error", __FILE__, __LINE__); } } } @@ -932,8 +935,8 @@ inflate_table( auto const not_enough = [] { - throw std::logic_error( - "insufficient output size when inflating tables"); + throw beast::detail::make_exception( + "insufficient output size when inflating tables", __FILE__, __LINE__); }; // check available table space diff --git a/scripts/build-and-test.sh b/scripts/build-and-test.sh index 3c8ed47ef..e62e93aa8 100755 --- a/scripts/build-and-test.sh +++ b/scripts/build-and-test.sh @@ -40,6 +40,9 @@ elif [[ $(uname -s) == "Linux" ]]; then if ((${num_proc_units} < ${num_jobs})); then num_jobs=$num_proc_units fi + if [[ "${TRAVIS}" == "true" && ${NUM_PROCESSORS:=2} > ${num_jobs} ]]; then + num_jobs=$NUM_PROCESSORS + fi fi echo "using toolset: $CC" @@ -99,10 +102,10 @@ function run_autobahn_test_suite { # We need to wait a while so wstest can connect! sleep 5 + # Show the output (if any) as it is generated + tail -f nohup.out & cd scripts && wstest -m fuzzingclient cd .. - # Show the output - cat nohup.out rm nohup.out # Show what jobs are running jobs @@ -110,6 +113,7 @@ function run_autobahn_test_suite { sleep 5 # Kill it gracefully kill -INT %1 + kill -INT %2 # Wait for all the jobs to finish wait # Parse the test results, with python>=2.5<3 script @@ -135,9 +139,10 @@ if [[ $VARIANT == "coverage" ]]; then # Perform test if [[ $MAIN_BRANCH == "1" ]]; then run_tests_with_valgrind - run_autobahn_test_suite + # skip slow autobahn tests + #run_autobahn_test_suite else - echo "skipping autobahn tests for feature branch build" + echo "skipping autobahn/valgrind tests for feature branch build" run_tests fi diff --git a/test/http/message.cpp b/test/http/message.cpp index d5db86e08..47216b236 100644 --- a/test/http/message.cpp +++ b/test/http/message.cpp @@ -278,6 +278,19 @@ public: BEAST_EXPECT(m2.fields.exists("h")); } + void + testSpecialMembers() + { + response r1; + response r2{r1}; + response r3{std::move(r2)}; + r2 = r3; + r1 = std::move(r2); + [r1]() + { + }(); + } + void run() override { testMessage(); @@ -285,6 +298,7 @@ public: testFreeFunctions(); testPrepare(); testSwap(); + testSpecialMembers(); } }; diff --git a/test/websocket/stream.cpp b/test/websocket/stream.cpp index 084019ebe..53a16bdd9 100644 --- a/test/websocket/stream.cpp +++ b/test/websocket/stream.cpp @@ -1416,7 +1416,10 @@ public: yield_to_mf(ep, &stream_test::testAsyncClient); } { - async_echo_server server(true, any, 4); + error_code ec; + async_echo_server server{nullptr, 4}; + server.open(true, any, ec); + BEAST_EXPECTS(! ec, ec.message()); auto const ep = server.local_endpoint(); testSyncClient(ep); testAsyncWriteFrame(ep); diff --git a/test/websocket/websocket_async_echo_server.hpp b/test/websocket/websocket_async_echo_server.hpp index ae4963243..0519deb20 100644 --- a/test/websocket/websocket_async_echo_server.hpp +++ b/test/websocket/websocket_async_echo_server.hpp @@ -18,6 +18,8 @@ #include #include +#include + namespace beast { namespace websocket { @@ -31,38 +33,24 @@ public: using socket_type = boost::asio::ip::tcp::socket; private: - bool log_ = false; + std::ostream* log_; boost::asio::io_service ios_; socket_type sock_; boost::asio::ip::tcp::acceptor acceptor_; std::vector thread_; + boost::optional work_; public: - async_echo_server(bool server, - endpoint_type const& ep, std::size_t threads) - : sock_(ios_) + async_echo_server(async_echo_server const&) = delete; + async_echo_server& operator=(async_echo_server const&) = delete; + + async_echo_server(std::ostream* log, + std::size_t threads) + : log_(log) + , sock_(ios_) , acceptor_(ios_) + , work_(ios_) { - if(server) - { - error_code ec; - acceptor_.open(ep.protocol(), ec); - maybe_throw(ec, "open"); - acceptor_.set_option( - boost::asio::socket_base::reuse_address{true}); - acceptor_.bind(ep, ec); - maybe_throw(ec, "bind"); - acceptor_.listen( - boost::asio::socket_base::max_connections, ec); - maybe_throw(ec, "listen"); - acceptor_.async_accept(sock_, - std::bind(&async_echo_server::on_accept, this, - beast::asio::placeholders::error)); - } - else - { - Peer{log_, std::move(sock_), ep}; - } thread_.reserve(threads); for(std::size_t i = 0; i < threads; ++i) thread_.emplace_back( @@ -71,6 +59,7 @@ public: ~async_echo_server() { + work_ = boost::none; error_code ec; ios_.dispatch( [&]{ acceptor_.close(ec); }); @@ -78,6 +67,46 @@ public: t.join(); } + void + open(bool server, + endpoint_type const& ep, error_code& ec) + { + if(server) + { + acceptor_.open(ep.protocol(), ec); + if(ec) + { + if(log_) + (*log_) << "open: " << ec.message() << std::endl; + return; + } + acceptor_.set_option( + boost::asio::socket_base::reuse_address{true}); + acceptor_.bind(ep, ec); + if(ec) + { + if(log_) + (*log_) << "bind: " << ec.message() << std::endl; + return; + } + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + { + if(log_) + (*log_) << "listen: " << ec.message() << std::endl; + return; + } + acceptor_.async_accept(sock_, + std::bind(&async_echo_server::on_accept, this, + beast::asio::placeholders::error)); + } + else + { + Peer{*this, std::move(sock_), ep}; + } + } + endpoint_type local_endpoint() const { @@ -89,7 +118,7 @@ private: { struct data { - bool log; + async_echo_server& server; int state = 0; boost::optional ep; stream ws; @@ -98,8 +127,9 @@ private: beast::streambuf db; int id; - data(bool log_, socket_type&& sock_) - : log(log_) + data(async_echo_server& server_, + socket_type&& sock_) + : server(server_) , ws(std::move(sock_)) , strand(ws.get_io_service()) , id([] @@ -110,9 +140,9 @@ private: { } - data(bool log_, socket_type&& sock_, - endpoint_type const& ep_) - : log(log_) + data(async_echo_server& server_, + socket_type&& sock_, endpoint_type const& ep_) + : server(server_) , ep(ep_) , ws(std::move(sock_)) , strand(ws.get_io_service()) @@ -152,14 +182,17 @@ private: template explicit - Peer(bool log, socket_type&& sock, Args&&... args) - : d_(std::make_shared(log, + Peer(async_echo_server& server, + socket_type&& sock, Args&&... args) + : d_(std::make_shared(server, std::forward(sock), std::forward(args)...)) { auto& d = *d_; d.ws.set_option(decorate(identity{})); d.ws.set_option(read_message_max(64 * 1024 * 1024)); + d.ws.set_option(auto_fragment{false}); + //d.ws.set_option(write_buffer_size{64 * 1024}); run(); } @@ -289,10 +322,10 @@ private: fail(error_code ec, std::string what) { auto& d = *d_; - if(d.log) + if(d.server.log_) { if(ec != error::closed) - std::cerr << "#" << d_->id << " " << + (*d.server.log_) << "#" << d.id << " " << what << ": " << ec.message() << std::endl; } } @@ -302,7 +335,7 @@ private: fail(error_code ec, std::string what) { if(log_) - std::cerr << what << ": " << + (*log_) << what << ": " << ec.message() << std::endl; } @@ -328,7 +361,7 @@ private: acceptor_.async_accept(sock_, std::bind(&async_echo_server::on_accept, this, beast::asio::placeholders::error)); - Peer{false, std::move(sock)}; + Peer{*this, std::move(sock)}; } }; diff --git a/test/websocket/websocket_echo.cpp b/test/websocket/websocket_echo.cpp index 636087b4c..a4ddee49c 100644 --- a/test/websocket/websocket_echo.cpp +++ b/test/websocket/websocket_echo.cpp @@ -8,17 +8,27 @@ #include "websocket_async_echo_server.hpp" #include "websocket_sync_echo_server.hpp" #include +#include int main() { using endpoint_type = boost::asio::ip::tcp::endpoint; using address_type = boost::asio::ip::address; - beast::websocket::async_echo_server s1(true, endpoint_type{ - address_type::from_string("127.0.0.1"), 6000 }, 4); + try + { + boost::system::error_code ec; + beast::websocket::async_echo_server s1{nullptr, 1}; + s1.open(true, endpoint_type{ + address_type::from_string("127.0.0.1"), 6000 }, ec); - beast::websocket::sync_echo_server s2(true, endpoint_type{ - address_type::from_string("127.0.0.1"), 6001 }); + beast::websocket::sync_echo_server s2(true, endpoint_type{ + address_type::from_string("127.0.0.1"), 6001 }); - beast::test::sig_wait(); + beast::test::sig_wait(); + } + catch(std::exception const& e) + { + std::cout << "Error: " << e.what() << std::endl; + } }