Skip to content

bad executor exception using a timeout in the client #1599

Description

@Ban44n

Hi,

We are using boost beast in a high performance manner, i.e. we need to be able to communicate >1000 transactions per second per connection. For our application it is crucial to use a timeout on the stream which is provided by your library.

We encounter a critical exception when a client has a timeout policy enabled (which sends ping/pong messages by the beast library) and when we stress the async_write function (see example client below) by sending messages all the time. The bug results in an unhandled exception:

terminate called after throwing an instance of 'boost::wrapexcept<boost::asio::bad_executor>'
  what():  bad executor
Aborted (core dumped)

Stack trace:

__GI_raise(int sig) (/build/glibc-KRRWSm/glibc-2.29/sysdeps/unix/sysv/linux/raise.c:50)
__GI_abort() (/build/glibc-KRRWSm/glibc-2.29/stdlib/abort.c:79)
[Unknown/Just-In-Time compiled code] (Unknown Source:0)
std::terminate() (Unknown Source:0)
__cxa_throw (Unknown Source:0)
boost::throw_exception<boost::asio::bad_executor>(const boost::asio::bad_executor & e) (/usr/local/include/boost/throw_exception.hpp:70)
boost::asio::executor::get_impl(const boost::asio::executor * const this) (/usr/local/include/boost/asio/executor.hpp:309)
boost::asio::executor::post<boost::asio::detail::work_dispatcher<boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::executor, boost::beast::unlimited_rate_policy> >, true>::idle_ping_op<boost::asio::executor> >, std::allocator<void> >(const boost::asio::executor * const this, boost::asio::detail::work_dispatcher<boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::executor, boost::beast::unlimited_rate_policy> > >::idle_ping_op<boost::asio::executor> > && f, const std::allocator<void> & a) (/usr/local/include/boost/asio/impl/executor.hpp:358)
boost::asio::detail::initiate_post::operator()<boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::executor, boost::beast::unlimited_rate_policy> >, true>::idle_ping_op<boost::asio::executor>, boost::asio::executor const&>(const boost::asio::detail::initiate_post * const this, boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::executor, boost::beast::unlimited_rate_policy> >, true>::idle_ping_op<boost::asio::executor> && handler, const boost::asio::executor & ex) (/usr/local/include/boost/asio/impl/post.hpp:54)
boost::asio::async_result<boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::executor, boost::beast::unlimited_rate_policy> >, true>::idle_ping_op<boost::asio::executor>, void ()>::initiate<boost::asio::detail::initiate_post, boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::executor, boost::beast::unlimited_rate_policy> >, true>::idle_ping_op<boost::asio::executor>, boost::asio::executor const&>(boost::asio::detail::initiate_post&&, boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::executor, boost::beast::unlimited_rate_policy> >, true>::idle_ping_op<boost::asio::executor>&&, boost::asio::executor const&)(boost::asio::detail::initiate_post && initiation, boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::executor, boost::beast::unlimited_rate_policy> >, true>::idle_ping_op<boost::asio::executor> && token,  args#0) (/usr/local/include/boost/asio/async_result.hpp:82)
boost::asio::async_initiate<boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::executor, boost::beast::unlimited_rate_policy> >, true>::idle_ping_op<boost::asio::executor>, void (), boost::asio::detail::initiate_post, boost::asio::executor const&>(boost::asio::detail::initiate_post&&, boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::executor, boost::beast::unlimited_rate_policy> >, true>::idle_ping_op<boost::asio::executor>&, boost::asio::executor const&)(boost::asio::detail::initiate_post && initiation, boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::executor, boost::beast::unlimited_rate_policy> >, true>::idle_ping_op<boost::asio::executor> & token,  args#0) (/usr/local/include/boost/asio/async_result.hpp:257)
boost::asio::post<boost::asio::executor, boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::executor, boost::beast::unlimited_rate_policy> >, true>::idle_ping_op<boost::asio::executor> >(const boost::asio::executor & ex, boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::executor, boost::beast::unlimited_rate_policy> >, true>::idle_ping_op<boost::asio::executor> && token) (/usr/local/include/boost/asio/impl/post.hpp:74)
boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::executor, boost::beast::unlimited_rate_policy> >, true>::idle_ping_op<boost::asio::executor>::operator()(boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::basic_stream<boost::asio::ip::tcp, boost::asio::executor, boost::beast::unlimited_rate_policy> >, true>::idle_ping_op<boost::asio::executor> * const this, boost::beast::error_code ec, std::size_t bytes_transferred) (/usr/local/include/boost/beast/websocket/impl/ping.hpp:179)
....

It seems that op_idle_ping implementation in the beast library cannot find the strand to execute the ping operation and an exception is raised (see websocket/impl/ping.hpp:179, I am not sure though). It is our belief that the library should function perfectly fine when the async_write function is properly stressed like our example.

Version of Beast

#define BOOST_BEAST_VERSION 248
(using boost 1_70_00)

Steps necessary to reproduce the problem

We have adjusted the client websocket async c++ example to reproduce the bug. Basically we added the timeout function with ping enabled and we keep on sending the message on the stream in a proper way.

//
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//

//------------------------------------------------------------------------------
//
// Example: WebSocket SSL client, asynchronous
//
//------------------------------------------------------------------------------

#include "certificates.hpp"

#include <boost/beast/core.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/beast/websocket/ssl.hpp>
#include <boost/asio/strand.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <thread>

namespace beast = boost::beast;         // from <boost/beast.hpp>
namespace http = beast::http;           // from <boost/beast/http.hpp>
namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
namespace net = boost::asio;            // from <boost/asio.hpp>
namespace ssl = boost::asio::ssl;       // from <boost/asio/ssl.hpp>
using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>

//------------------------------------------------------------------------------

// Report a failure
void fail(beast::error_code ec, char const *what)
{
  std::cerr << what << ": " << ec.message() << "\n";
}

// Sends a WebSocket message and prints the response
class session : public std::enable_shared_from_this<session>
{
  tcp::resolver resolver_;
  websocket::stream<
      beast::ssl_stream<beast::tcp_stream>>
      ws_;
  beast::flat_buffer buffer_;
  std::string host_;
  std::string text_;

public:
  // Resolver and socket require an io_context
  explicit session(net::io_context &ioc, ssl::context &ctx)
      : resolver_(net::make_strand(ioc)), ws_(net::make_strand(ioc), ctx)
  {
  }

  // Start the asynchronous operation
  void
  run(
      char const *host,
      char const *port,
      char const *text)
  {
    // Save these for later
    host_ = host;
    text_ = text;

    // Look up the domain name
    resolver_.async_resolve(
        host,
        port,
        beast::bind_front_handler(
            &session::on_resolve,
            shared_from_this()));
  }

  void
  on_resolve(
      beast::error_code ec,
      tcp::resolver::results_type results)
  {
    if (ec)
      return fail(ec, "resolve");

    // Set a timeout on the operation
    beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30));

    // Make the connection on the IP address we get from a lookup
    beast::get_lowest_layer(ws_).async_connect(
        results,
        beast::bind_front_handler(
            &session::on_connect,
            shared_from_this()));
  }

  void
  on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type)
  {
    if (ec)
      return fail(ec, "connect");

    // Set a timeout on the operation
    beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30));

    // Perform the SSL handshake
    ws_.next_layer().async_handshake(
        ssl::stream_base::client,
        beast::bind_front_handler(
            &session::on_ssl_handshake,
            shared_from_this()));
  }

  void
  on_ssl_handshake(beast::error_code ec)
  {
    if (ec)
      return fail(ec, "ssl_handshake");

    // Turn off the timeout on the tcp_stream, because
    // the websocket stream has its own timeout system.
    beast::get_lowest_layer(ws_).expires_never();

    // Set timeout
    auto timeout = 3;
    beast::websocket::stream_base::timeout opt{
        std::chrono::seconds(timeout), // close timeout
        std::chrono::seconds(timeout), // idle timeout
        true};                         // pings enabled (results in weird bug)
    ws_.set_option(opt);

    // Perform the websocket handshake
    ws_.async_handshake(host_, "/",
                        beast::bind_front_handler(
                            &session::on_handshake,
                            shared_from_this()));
  }

  void
  on_handshake(beast::error_code ec)
  {
    if (ec)
      return fail(ec, "handshake");

    // Start writing to the stream
    ws_.async_write(
        net::buffer(text_),
        beast::bind_front_handler(
            &session::on_write,
            shared_from_this()));

    // Start reading from the stream
    ws_.async_read(
        buffer_,
        beast::bind_front_handler(
            &session::on_read,
            shared_from_this()));
  }

  void
  on_write(
      beast::error_code ec,
      std::size_t bytes_transferred)
  {
    boost::ignore_unused(bytes_transferred);

    if (ec)
      return fail(ec, "write");

    // Write again
    ws_.async_write(
        net::buffer(text_),
        beast::bind_front_handler(
            &session::on_write,
            shared_from_this()));
  }

  void
  on_read(
      beast::error_code ec,
      std::size_t bytes_transferred)
  {
    boost::ignore_unused(bytes_transferred);

    if (ec)
      return fail(ec, "read");

    // Clear buffer
    buffer_.clear();

    // Read again
    ws_.async_read(
        buffer_,
        beast::bind_front_handler(
            &session::on_read,
            shared_from_this()));
  }
};

//------------------------------------------------------------------------------

int main(int argc, char **argv)
{
  // Check command line arguments.
  if (argc != 4)
  {
    std::cerr << "Usage: websocket-client-async-ssl <host> <port> <text>\n"
              << "Example:\n"
              << "    websocket-client-async-ssl echo.websocket.org 443 \"Hello, world!\"\n";
    return EXIT_FAILURE;
  }
  auto const host = argv[1];
  auto const port = argv[2];
  auto const text = argv[3];

  // The io_context is required for all I/O
  boost::asio::io_context ioc;

  // The SSL context is required, and holds certificates
  ssl::context ctx{ssl::context::tlsv13};

  // Launch the asynchronous operation

  std::make_shared<session>(ioc, ctx)->run(host, port, text);

  ioc.run();

  return EXIT_SUCCESS;
}

All relevant compiler information

We are using ubuntu 19.04 and gnu compiler

g++ (Ubuntu 8.3.0-6ubuntu1) 8.3.0

The server can be anything, a quick running server in nodejs is for example (using the certificate and key of your example directory):

const WebSocket = require('ws');
const fs = require('fs');
const https = require('https');

let cert = `-----BEGIN CERTIFICATE-----
MIIDaDCCAlCgAwIBAgIJAO8vBu8i8exWMA0GCSqGSIb3DQEBCwUAMEkxCzAJBgNV
BAYTAlVTMQswCQYDVQQIDAJDQTEtMCsGA1UEBwwkTG9zIEFuZ2VsZXNPPUJlYXN0
Q049d3d3LmV4YW1wbGUuY29tMB4XDTE3MDUwMzE4MzkxMloXDTQ0MDkxODE4Mzkx
MlowSTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMS0wKwYDVQQHDCRMb3MgQW5n
ZWxlc089QmVhc3RDTj13d3cuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQDJ7BRKFO8fqmsEXw8v9YOVXyrQVsVbjSSGEs4Vzs4cJgcF
xqGitbnLIrOgiJpRAPLy5MNcAXE1strVGfdEf7xMYSZ/4wOrxUyVw/Ltgsft8m7b
Fu8TsCzO6XrxpnVtWk506YZ7ToTa5UjHfBi2+pWTxbpN12UhiZNUcrRsqTFW+6fO
9d7xm5wlaZG8cMdg0cO1bhkz45JSl3wWKIES7t3EfKePZbNlQ5hPy7Pd5JTmdGBp
yY8anC8u4LPbmgW0/U31PH0rRVfGcBbZsAoQw5Tc5dnb6N2GEIbq3ehSfdDHGnrv
enu2tOK9Qx6GEzXh3sekZkxcgh+NlIxCNxu//Dk9AgMBAAGjUzBRMB0GA1UdDgQW
BBTZh0N9Ne1OD7GBGJYz4PNESHuXezAfBgNVHSMEGDAWgBTZh0N9Ne1OD7GBGJYz
4PNESHuXezAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCmTJVT
LH5Cru1vXtzb3N9dyolcVH82xFVwPewArchgq+CEkajOU9bnzCqvhM4CryBb4cUs
gqXWp85hAh55uBOqXb2yyESEleMCJEiVTwm/m26FdONvEGptsiCmF5Gxi0YRtn8N
V+KhrQaAyLrLdPYI7TrwAOisq2I1cD0mt+xgwuv/654Rl3IhOMx+fKWKJ9qLAiaE
fQyshjlPP9mYVxWOxqctUdQ8UnsUKKGEUcVrA08i1OAnVKlPFjKBvk+r7jpsTPcr
9pWXTO9JrYMML7d+XRSZA1n3856OqZDX4403+9FnXCvfcLZLLKTBvwwFgEFGpzjK
UEVbkhd5qstF6qWK
-----END CERTIFICATE-----`;

let key = `-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJ7BRKFO8fqmsE
Xw8v9YOVXyrQVsVbjSSGEs4Vzs4cJgcFxqGitbnLIrOgiJpRAPLy5MNcAXE1strV
GfdEf7xMYSZ/4wOrxUyVw/Ltgsft8m7bFu8TsCzO6XrxpnVtWk506YZ7ToTa5UjH
fBi2+pWTxbpN12UhiZNUcrRsqTFW+6fO9d7xm5wlaZG8cMdg0cO1bhkz45JSl3wW
KIES7t3EfKePZbNlQ5hPy7Pd5JTmdGBpyY8anC8u4LPbmgW0/U31PH0rRVfGcBbZ
sAoQw5Tc5dnb6N2GEIbq3ehSfdDHGnrvenu2tOK9Qx6GEzXh3sekZkxcgh+NlIxC
Nxu//Dk9AgMBAAECggEBAK1gV8uETg4SdfE67f9v/5uyK0DYQH1ro4C7hNiUycTB
oiYDd6YOA4m4MiQVJuuGtRR5+IR3eI1zFRMFSJs4UqYChNwqQGys7CVsKpplQOW+
1BCqkH2HN/Ix5662Dv3mHJemLCKUON77IJKoq0/xuZ04mc9csykox6grFWB3pjXY
OEn9U8pt5KNldWfpfAZ7xu9WfyvthGXlhfwKEetOuHfAQv7FF6s25UIEU6Hmnwp9
VmYp2twfMGdztz/gfFjKOGxf92RG+FMSkyAPq/vhyB7oQWxa+vdBn6BSdsfn27Qs
bTvXrGe4FYcbuw4WkAKTljZX7TUegkXiwFoSps0jegECgYEA7o5AcRTZVUmmSs8W
PUHn89UEuDAMFVk7grG1bg8exLQSpugCykcqXt1WNrqB7x6nB+dbVANWNhSmhgCg
VrV941vbx8ketqZ9YInSbGPWIU/tss3r8Yx2Ct3mQpvpGC6iGHzEc/NHJP8Efvh/
CcUWmLjLGJYYeP5oNu5cncC3fXUCgYEA2LANATm0A6sFVGe3sSLO9un1brA4zlZE
Hjd3KOZnMPt73B426qUOcw5B2wIS8GJsUES0P94pKg83oyzmoUV9vJpJLjHA4qmL
CDAd6CjAmE5ea4dFdZwDDS8F9FntJMdPQJA9vq+JaeS+k7ds3+7oiNe+RUIHR1Sz
VEAKh3Xw66kCgYB7KO/2Mchesu5qku2tZJhHF4QfP5cNcos511uO3bmJ3ln+16uR
GRqz7Vu0V6f7dvzPJM/O2QYqV5D9f9dHzN2YgvU9+QSlUeFK9PyxPv3vJt/WP1//
zf+nbpaRbwLxnCnNsKSQJFpnrE166/pSZfFbmZQpNlyeIuJU8czZGQTifQKBgHXe
/pQGEZhVNab+bHwdFTxXdDzr+1qyrodJYLaM7uFES9InVXQ6qSuJO+WosSi2QXlA
hlSfwwCwGnHXAPYFWSp5Owm34tbpp0mi8wHQ+UNgjhgsE2qwnTBUvgZ3zHpPORtD
23KZBkTmO40bIEyIJ1IZGdWO32q79nkEBTY+v/lRAoGBAI1rbouFYPBrTYQ9kcjt
1yfu4JF5MvO9JrHQ9tOwkqDmNCWx9xWXbgydsn/eFtuUMULWsG3lNjfst/Esb8ch
k5cZd6pdJZa4/vhEwrYYSuEjMCnRb0lUsm7TsHxQrUd6Fi/mUuFU/haC0o0chLq7
pVOUFq5mW8p0zbtfHbjkgxyF
-----END PRIVATE KEY-----`;

const server = https.createServer({
  key: key,
  cert: cert
});
const wss = new WebSocket.Server({ server });


wss.on('connection', function connection(ws) {
  ws.on('ping', () => {
    console.log('received ping');
  });
  ws.on('pong', () => {
    console.log('received pong');
  });
  ws.on('message', function incoming(message) {
    console.log('received: %s', message);
  });
});

// Start listen
server.listen(8080);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions