beast support per-op cancellation

- websocket supports cancellation.
 - Iterating test for ws cancellation.
 - Only terminal cancellation is forwarded by default.
 - basic_stream supports cancellation.
 - supported cancellation is documented.
 - http cancellation additions.
 - Added cancellation_slot tests to http, utils and saved_handler.
 - Added post to write.cpp, to avoid SIGSEV in test.
 - Refresher describes cancellation in more detail.
This commit is contained in:
Klemens authored and Klemens Morgenstern committed 2022-10-27 22:56:19 +08:00
1 parent 0bf3d971a0
commit 3ebff60b1a
35 files changed
+1216 -29

No files matched your search

+62 -1
View File
@@ -25,6 +25,12 @@
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/connect_pipe.hpp>
#include <boost/asio/readable_pipe.hpp>
#include <boost/asio/writable_pipe.hpp>
#include <atomic>
#if BOOST_ASIO_HAS_CO_AWAIT
@@ -729,7 +735,57 @@ public:
}
}
void
testCancellation(yield_context do_yield)
{
// this is tested on a pipe
// because the test::stream doesn't implement cancellation
{
response<string_body> m;
error_code ec;
net::writable_pipe ts{ioc_};
net::readable_pipe tr{ioc_};
net::connect_pipe(tr, ts);
net::cancellation_signal cl;
net::post(ioc_, [&]{cl.emit(net::cancellation_type::all);});
net::steady_timer timeout(ioc_, std::chrono::seconds(5));
timeout.async_wait(
[&](error_code ec)
{
BEAST_EXPECT(ec == net::error::operation_aborted);
if (!ec) // this means the cancel failed!
ts.close();
});
multi_buffer b;
async_read(tr, b, m, net::bind_cancellation_slot(cl.slot(), do_yield[ec]));
timeout.cancel();
BEAST_EXPECT(ec == net::error::operation_aborted);
}
{
response<string_body> m;
error_code ec;
net::writable_pipe ts{ioc_};
net::readable_pipe tr{ioc_};
net::connect_pipe(tr, ts);
net::cancellation_signal cl;
net::post(ioc_, [&]{cl.emit(net::cancellation_type::all);});
net::steady_timer timeout(ioc_, std::chrono::seconds(5));
timeout.async_wait(
[&](error_code ec)
{
// using BEAST_EXPECT HERE is a race condition, since the test suite might
BEAST_EXPECT(ec == net::error::operation_aborted);
if (!ec) // this means the cancel failed!
ts.close();
});
multi_buffer b;
async_read(tr, b, m, net::bind_cancellation_slot(cl.slot(), do_yield[ec]));
timeout.cancel();
BEAST_EXPECT(ec == net::error::operation_aborted);
}
// the timer handler may be invoked after the test suite is complete if we don't post.
asio::post(ioc_, do_yield);
}
void
run() override
{
@@ -761,6 +817,11 @@ public:
testReadSomeHeader(yield);
});
testReadSomeHeader();
yield_to(
[&](yield_context yield)
{
testCancellation(yield);
});
}
+73
View File
@@ -22,8 +22,13 @@
#include <boost/beast/_experimental/test/stream.hpp>
#include <boost/beast/test/yield_to.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/connect_pipe.hpp>
#include <boost/asio/readable_pipe.hpp>
#include <boost/asio/writable_pipe.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/strand.hpp>
#include <sstream>
#include <string>
@@ -1050,6 +1055,69 @@ public:
}
#endif
void
testCancellation(yield_context do_yield)
{
// this is tested on a pipe
// because the test::stream doesn't implement cancellation
{
response<string_body> m;
m.version(10);
m.result(status::ok);
m.set(field::server, "test");
m.set(field::content_length, "5");
// make the content big enough so it overflows the buffer
// that'll make the op never complete if we don't cancel
m.body().assign(10000000, '*');
error_code ec;
net::writable_pipe ts{ioc_};
net::readable_pipe tr{ioc_};
net::connect_pipe(tr, ts);
net::cancellation_signal cl;
net::post(ioc_, [&]{cl.emit(net::cancellation_type::all);});
net::steady_timer timeout(ioc_, std::chrono::seconds(5));
timeout.async_wait(
[&](error_code ec)
{
BEAST_EXPECT(ec == net::error::operation_aborted);
if (!ec) // this means the cancel failed!
ts.close();
});
async_write(ts, m, net::bind_cancellation_slot(cl.slot(), do_yield[ec]));
timeout.cancel();
net::post(ioc_, do_yield); // wait for the timeout to finish
BEAST_EXPECT(ec == net::error::operation_aborted);
}
{
response<string_body> m;
m.version(11);
m.result(status::ok);
m.set(field::server, "test");
m.set(field::transfer_encoding, "chunked");
m.body().assign(10000000, '*');
error_code ec;
net::writable_pipe ts{ioc_};
net::readable_pipe tr{ioc_};
net::connect_pipe(tr, ts);
net::cancellation_signal cl;
net::post(ioc_, [&]{cl.emit(net::cancellation_type::all);});
net::steady_timer timeout(ioc_, std::chrono::seconds(5));
timeout.async_wait(
[&](error_code ec)
{
BEAST_EXPECT(ec == net::error::operation_aborted);
if (!ec) // this means the cancel failed!
ts.close();
});
async_write(ts, m, net::bind_cancellation_slot(cl.slot(), do_yield[ec]));
timeout.cancel();
net::post(ioc_, do_yield); // wait for the timeout to finish
BEAST_EXPECT(ec == net::error::operation_aborted);
}
// the timer handler may be invoked after the test suite is complete if we don't post.
asio::post(ioc_, do_yield);
}
void
run() override
@@ -1077,6 +1145,11 @@ public:
#if BOOST_ASIO_HAS_CO_AWAIT
boost::ignore_unused(&write_test::testAwaitableCompiles);
#endif
yield_to(
[&](yield_context yield)
{
testCancellation(yield);
});
}
};