16
16
#include < boost/asio/any_completion_handler.hpp>
17
17
#include < boost/asio/any_io_executor.hpp>
18
18
#include < boost/asio/async_result.hpp>
19
+ #include < boost/asio/bind_cancellation_slot.hpp>
19
20
#include < boost/asio/bind_executor.hpp>
20
21
#include < boost/asio/buffer.hpp>
22
+ #include < boost/asio/cancellation_signal.hpp>
23
+ #include < boost/asio/cancellation_type.hpp>
21
24
#include < boost/asio/coroutine.hpp>
22
25
#include < boost/asio/deferred.hpp>
23
26
#include < boost/asio/dispatch.hpp>
29
32
#include < cstddef>
30
33
#include < cstdint>
31
34
#include < cstring>
35
+ #include < ostream>
32
36
33
37
#include " test_common/create_diagnostics.hpp"
34
38
#include " test_common/io_context_fixture.hpp"
@@ -41,6 +45,7 @@ using namespace boost::mysql::test;
41
45
using namespace boost ::mysql::detail;
42
46
namespace asio = boost::asio;
43
47
using boost::mysql::error_code;
48
+ using boost::test_tools::per_element;
44
49
45
50
BOOST_AUTO_TEST_SUITE (test_engine_impl)
46
51
@@ -230,7 +235,21 @@ using signature_t = netmaker_t::signature;
230
235
// A mock for a sans-io algorithm. Can be converted to any_resumable_ref
231
236
struct mock_algo
232
237
{
233
- using call_args = std::pair<error_code, std::size_t >;
238
+ struct call_args
239
+ {
240
+ error_code ec;
241
+ std::size_t bytes_transferred;
242
+
243
+ bool operator ==(const call_args& rhs) const
244
+ {
245
+ return ec == rhs.ec && bytes_transferred == rhs.bytes_transferred ;
246
+ }
247
+
248
+ friend std::ostream& operator <<(std::ostream& os, const call_args& v)
249
+ {
250
+ return os << " {" << v.ec << " , " << v.bytes_transferred << " }" ;
251
+ }
252
+ };
234
253
235
254
std::size_t current_call{0 };
236
255
std::vector<next_action> acts;
@@ -239,7 +258,7 @@ struct mock_algo
239
258
mock_algo (next_action act) : acts{act} {}
240
259
mock_algo (next_action act1, next_action act2) : acts{act1, act2} {}
241
260
242
- void check_calls (const std::vector<call_args>& expected) { BOOST_TEST (calls == expected); }
261
+ void check_calls (const std::vector<call_args>& expected) { BOOST_TEST (calls == expected, per_element () ); }
243
262
244
263
next_action resume (error_code ec, std::size_t bytes_transferred)
245
264
{
@@ -559,4 +578,102 @@ BOOST_AUTO_TEST_CASE(resume_error_successive_calls)
559
578
}
560
579
}
561
580
581
+ // Regression tests for https://github.com/boostorg/mysql/issues/199
582
+ // If a cancellation signal is emitted after an operation completes successfully
583
+ // and before its handler gets called, engine uses the composed op's cancellation
584
+ // state and resumes the algorithm with a cancelled error code
585
+ BOOST_FIXTURE_TEST_CASE (cancel_during_post, io_context_fixture)
586
+ {
587
+ // Setup
588
+ mock_algo algo (next_action::ssl_handshake ());
589
+ test_engine eng{ctx.get_executor ()};
590
+ asio::cancellation_signal sig;
591
+
592
+ // Start the operation. The mock stream will immediately complete the requested operation
593
+ // by calling asio::post.
594
+ auto run_result = eng.async_run (
595
+ any_resumable_ref (algo),
596
+ asio::bind_cancellation_slot (sig.slot (), as_netresult)
597
+ );
598
+
599
+ // At this point, the completion hasn't been called yet, since run() hasn't been called.
600
+ // Emit the cancellation signal
601
+ sig.emit (asio::cancellation_type_t ::terminal);
602
+
603
+ // Run until completion
604
+ std::move (run_result).validate_no_error_nodiag ();
605
+
606
+ // The algorithm was resumed with the relevant error
607
+ BOOST_TEST (eng.value .stream ().calls .size () == 1u );
608
+ BOOST_TEST (eng.value .stream ().calls [0 ].type () == next_action_type::ssl_handshake);
609
+ algo.check_calls ({
610
+ {error_code (), 0u },
611
+ {asio::error::operation_aborted, 0u }
612
+ });
613
+ }
614
+
615
+ // We do nothing for cancellation types != terminal
616
+ BOOST_FIXTURE_TEST_CASE (cancel_during_post_cancel_type_non_terminal, io_context_fixture)
617
+ {
618
+ // Setup
619
+ mock_algo algo (next_action::ssl_handshake ());
620
+ test_engine eng{ctx.get_executor ()};
621
+ asio::cancellation_signal sig;
622
+
623
+ // Start the operation. The mock stream will immediately complete the requested operation
624
+ // by calling asio::post.
625
+ auto run_result = eng.async_run (
626
+ any_resumable_ref (algo),
627
+ asio::bind_cancellation_slot (sig.slot (), as_netresult)
628
+ );
629
+
630
+ // At this point, the completion hasn't been called yet, since run() hasn't been called.
631
+ // Emit the cancellation signal
632
+ sig.emit (asio::cancellation_type_t ::total);
633
+
634
+ // Run until completion
635
+ std::move (run_result).validate_no_error_nodiag ();
636
+
637
+ // The cancellation is ignored because algorithms don't support total cancellation
638
+ BOOST_TEST (eng.value .stream ().calls .size () == 1u );
639
+ BOOST_TEST (eng.value .stream ().calls [0 ].type () == next_action_type::ssl_handshake);
640
+ algo.check_calls ({
641
+ {error_code (), 0u },
642
+ {error_code (), 0u }
643
+ });
644
+ }
645
+
646
+ // If the operation failed with another error code, we don't override it
647
+ BOOST_FIXTURE_TEST_CASE (cancel_during_post_other_error, io_context_fixture)
648
+ {
649
+ // Setup
650
+ mock_algo algo (next_action::ssl_handshake ());
651
+ test_engine eng{
652
+ {ctx.get_executor (), asio::error::network_reset}
653
+ };
654
+ asio::cancellation_signal sig;
655
+
656
+ // Start the operation. The mock stream will immediately complete the requested operation
657
+ // by calling asio::post.
658
+ auto run_result = eng.async_run (
659
+ any_resumable_ref (algo),
660
+ asio::bind_cancellation_slot (sig.slot (), as_netresult)
661
+ );
662
+
663
+ // At this point, the completion hasn't been called yet, since run() hasn't been called.
664
+ // Emit the cancellation signal
665
+ sig.emit (asio::cancellation_type_t ::terminal);
666
+
667
+ // Run until completion
668
+ std::move (run_result).validate_no_error_nodiag ();
669
+
670
+ // The cancellation didn't override the operation's error code
671
+ BOOST_TEST (eng.value .stream ().calls .size () == 1u );
672
+ BOOST_TEST (eng.value .stream ().calls [0 ].type () == next_action_type::ssl_handshake);
673
+ algo.check_calls ({
674
+ {error_code (), 0u },
675
+ {asio::error::network_reset, 0u }
676
+ });
677
+ }
678
+
562
679
BOOST_AUTO_TEST_SUITE_END ()
0 commit comments