Skip to content

Commit

Permalink
Update websocket idle timer upon receiving a ping message
Browse files Browse the repository at this point in the history
  • Loading branch information
ashtum committed Feb 8, 2025
1 parent 3b0c2c9 commit 05e18a4
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 67 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,13 @@ jobs:
- toolset: clang
install: clang-16
compiler: clang++-16
cxxstd: "11,14,17,20,2b"
cxxstd: "11,14,17,20" # no 2b: https://github.com/llvm/llvm-project/issues/97842
os: ubuntu-24.04
supported: true
- toolset: clang
install: clang-17
compiler: clang++-17
cxxstd: "11,14,17,20,2b"
cxxstd: "11,14,17,20" # no 2b: https://github.com/llvm/llvm-project/issues/97842
os: ubuntu-24.04
supported: true
- toolset: clang
Expand Down
2 changes: 2 additions & 0 deletions include/boost/beast/websocket/impl/read.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ class stream<NextLayer, deflateSupported>::read_some_op
// Handle ping frame
if(impl.rd_fh.op == detail::opcode::ping)
{
impl.update_timer(this->get_executor());

if(impl.ctrl_cb)
{
if(! cont)
Expand Down
12 changes: 8 additions & 4 deletions include/boost/beast/websocket/impl/stream_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -439,8 +439,12 @@ struct stream<NextLayer, deflateSupported>::impl_type
if(timeout_opt.idle_timeout != none())
{
idle_counter = 0;
timer.expires_after(
timeout_opt.idle_timeout / 2);
if(timeout_opt.keep_alive_pings)
timer.expires_after(
timeout_opt.idle_timeout / 2);
else
timer.expires_after(
timeout_opt.idle_timeout);

BOOST_ASIO_HANDLER_LOCATION((
__FILE__, __LINE__,
Expand Down Expand Up @@ -565,9 +569,9 @@ struct stream<NextLayer, deflateSupported>::impl_type
if(impl.timeout_opt.idle_timeout == none())
return;

if( impl.idle_counter < 1 )
if( impl.timeout_opt.keep_alive_pings &&
impl.idle_counter < 1)
{
if( impl.timeout_opt.keep_alive_pings )
{
BOOST_ASIO_HANDLER_LOCATION((
__FILE__, __LINE__,
Expand Down
106 changes: 51 additions & 55 deletions test/beast/websocket/ping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,92 +88,88 @@ class ping_test : public websocket_test_suite
se.code().message());
}
}
}

// inactivity timeout doesn't happen when you get pings
void
testPing()
{
doTestPing(SyncClient{});

yield_to([&](yield_context yield)
{
echo_server es{log};
stream<test::stream> ws{ioc_};
doTestPing(AsyncClient{yield});
});

// We have an inactivity timeout of 2s, but don't send pings
ws.set_option(stream_base::timeout{
// inactivity timeout doesn't happen when you get pings
{
net::io_context ioc;
stream<test::stream> ws1(ioc);
stream<test::stream> ws2(ioc);
test::connect(ws1.next_layer(), ws2.next_layer());
ws1.set_option(stream_base::timeout{
stream_base::none(),
std::chrono::milliseconds(2000),
std::chrono::milliseconds(500),
false
});
ws.next_layer().connect(es.stream());
ws.handshake("localhost", "/");
flat_buffer b;
ws2.async_accept(test::success_handler());
ws1.async_handshake("localhost", "/", test::success_handler());
ioc.run();
ioc.restart();
bool got_timeout = false;
ws.async_read(b,
flat_buffer b1;
ws1.async_read(b1,
[&](error_code ec, std::size_t)
{
if(ec != beast::error::timeout)
BOOST_THROW_EXCEPTION(
system_error{ec});
got_timeout = true;
});
// We are connected, base state
BEAST_EXPECT(ws.impl_->idle_counter == 0);

test::run_for(ioc_, std::chrono::milliseconds(1250));
// After 1.25s idle, no timeout but idle counter is 1
BEAST_EXPECT(ws.impl_->idle_counter == 1);

es.async_ping();
test::run_for(ioc_, std::chrono::milliseconds(500));
// The server sent a ping at 1.25s mark, and we're now at 1.75s mark.
// We haven't hit the idle timer yet (happens at 1s, 2s, 3s)
BEAST_EXPECT(ws.impl_->idle_counter == 0);
BEAST_EXPECT(!got_timeout);

test::run_for(ioc_, std::chrono::milliseconds(750));
// At 2.5s total; should have triggered the idle timer
BEAST_EXPECT(ws.impl_->idle_counter == 1);
ioc.run_for(std::chrono::milliseconds(250));
ioc.restart();
ws2.async_ping("", test::success_handler());
ioc.run_for(std::chrono::milliseconds(300));
ioc.restart();
BEAST_EXPECT(!got_timeout);

test::run_for(ioc_, std::chrono::milliseconds(750));
// At 3s total; should have triggered the idle timer again without
// activity and triggered timeout.
ioc.run_for(std::chrono::milliseconds(500));
ioc.restart();
BEAST_EXPECT(got_timeout);
}

// inactivity timeout doesn't happen when you send pings
{
echo_server es{log};
stream<test::stream> ws{ioc_};
ws.set_option(stream_base::timeout{
net::io_context ioc;
stream<test::stream> ws1(ioc);
stream<test::stream> ws2(ioc);
test::connect(ws1.next_layer(), ws2.next_layer());
ws1.set_option(stream_base::timeout{
stream_base::none(),
std::chrono::milliseconds(600),
std::chrono::milliseconds(800),
true
});
unsigned n_pongs = 0;
ws.control_callback({[&](frame_type kind, string_view)
ws1.control_callback({[&](frame_type kind, string_view)
{
if (kind == frame_type::pong)
++n_pongs;
}});
ws.next_layer().connect(es.stream());
ws.handshake("localhost", "/");
flat_buffer b;
ws.async_read(b, test::fail_handler(asio::error::operation_aborted));
// We are connected, base state
test::run_for(ioc_, std::chrono::seconds(1));
// About a second later, we should have close to 5 pings/pongs, and no timeout
BEAST_EXPECTS(2 <= n_pongs && n_pongs <= 3, "Unexpected nr of pings: " + std::to_string(n_pongs));
ws2.async_accept(test::success_handler());
ws1.async_handshake("localhost", "/", test::success_handler());
ioc.run();
ioc.restart();
flat_buffer b1, b2;
ws1.async_read(b1, test::fail_handler(asio::error::operation_aborted));
ws2.async_read(b2, test::fail_handler(error::closed));
ioc.run_for(std::chrono::seconds(1));
ioc.restart();
ws1.async_close({}, test::success_handler());
ioc.run();
ioc.restart();
// We should have close to 2 pings/pongs, and no timeout
BEAST_EXPECTS(1 <= n_pongs && n_pongs <= 3, "Unexpected nr of pings: " + std::to_string(n_pongs));
}
}

void
testPing()
{
doTestPing(SyncClient{});

yield_to([&](yield_context yield)
{
doTestPing(AsyncClient{yield});
});
}

void
testSuspend()
{
Expand Down
6 changes: 0 additions & 6 deletions test/beast/websocket/test.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,6 @@ class websocket_test_suite
this));
}

void
async_ping()
{
ws_.async_ping("", [](error_code){});
}

void
async_close()
{
Expand Down

0 comments on commit 05e18a4

Please sign in to comment.