beast: Boost 1.66 upgrade: assertion failure and mixing async/sync ops

I recently upgraded from Beast beta 0.69 to official Boost 1.66. (Belated congratz to Vinnie 😃

I have a SSL websocket client test application for sending various test traffic to my SSL websocket server application. This test client can create multiple concurrent websocket connections and multiple worker threads all managed under a single asio::io_service. Each connection session uses a mix of asynchronous reads and synchronous writes (including synchronous websocket::stream::close()). The async read callbacks are wrapped by a strand. The synchronous writes/close are executed from within strand-wrapped callbacks or strand-posted functions. There is at most one active async_read() at a time.

This test client worked fine with Beast 0.69. After upgrading to Boost 1.66, I noticed that when these client websocket connections attempt to initiate the close:

  1. websocket::stream::close() always produced the error code websocket::error::failed (“boost.beast.websocket: WebSocket connection failed due to a protocol violation”)
  2. Sometimes one or more of the connection sessions would assert fail at: beast/websocket/impl/read.ipp:576 Assertion `ws_.status_ == status::open’ failed.

As an experiment, I changed close() to async_close() and then I noticed:

  1. The error code websocket::error::failed went away. async_close’s callback always receives a success status.
  2. The read.ipp:576 “ws_.status_ == status::open” assertion failure went away.
  3. A new assertion failure appeared, but much less frequently: beast/websocket/impl/read.ipp:217 Assertion `ws_.rd_block_’ failed. The async_read() callback handler was not reached, so I’m guessing this asserted while processing the peer close frame response from the server.

Questions:

  1. Is it incorrect to mix async and sync calls?

I understand that there can be no more than one active async read, write, ping, close op without an intervening corresponding callback. I also understand that websocket objects are not thread safe so some kind of synchronization control (like an explicit strand) is needed when using multiple threads.

I think I’m following these rules… But perhaps if websocket::stream::close() is internally performing async ops on the websocket, then these async ops might get executed on another thread without strand protection. This could race with my earlier call to async_read() if the timing is unlucky and end up with two threads modifying the same websocket.

But the docs for close() says it’s implemented with the next layer’s write_some() which are blocking calls.

Not sure if this is my bug or Beast bug

  1. what might be causing the new assertion failure at read.ipp:217?

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 2
  • Comments: 86 (41 by maintainers)

Commits related to this issue

Most upvoted comments

The Boost.Asio author has informed me that overloads of asio_handler_invoke are still needed if composed operations are to work with the old-style strands (io_service::strand), which you are using. I am working on a fix which should resolve this issue, thanks for sticking through it. I expect that the non-SSL version will also malfunction.