]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // |
2 | // Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) | |
3 | // | |
4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |
5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
6 | // | |
7 | ||
8 | #ifndef BEAST_TEST_YIELD_TO_HPP | |
9 | #define BEAST_TEST_YIELD_TO_HPP | |
10 | ||
11 | #include <boost/asio/io_service.hpp> | |
12 | #include <boost/asio/spawn.hpp> | |
13 | #include <boost/optional.hpp> | |
14 | #include <condition_variable> | |
15 | #include <functional> | |
16 | #include <mutex> | |
17 | #include <thread> | |
18 | ||
19 | namespace beast { | |
20 | namespace test { | |
21 | ||
22 | /** Mix-in to support tests using asio coroutines. | |
23 | ||
24 | Derive from this class and use yield_to to launch test | |
25 | functions inside coroutines. This is handy for testing | |
26 | asynchronous asio code. | |
27 | */ | |
28 | class enable_yield_to | |
29 | { | |
30 | protected: | |
31 | boost::asio::io_service ios_; | |
32 | ||
33 | private: | |
34 | boost::optional<boost::asio::io_service::work> work_; | |
35 | std::thread thread_; | |
36 | std::mutex m_; | |
37 | std::condition_variable cv_; | |
38 | bool running_ = false; | |
39 | ||
40 | public: | |
41 | /// The type of yield context passed to functions. | |
42 | using yield_context = | |
43 | boost::asio::yield_context; | |
44 | ||
45 | enable_yield_to() | |
46 | : work_(ios_) | |
47 | , thread_([&] | |
48 | { | |
49 | ios_.run(); | |
50 | } | |
51 | ) | |
52 | { | |
53 | } | |
54 | ||
55 | ~enable_yield_to() | |
56 | { | |
57 | work_ = boost::none; | |
58 | thread_.join(); | |
59 | } | |
60 | ||
61 | /// Return the `io_service` associated with the object | |
62 | boost::asio::io_service& | |
63 | get_io_service() | |
64 | { | |
65 | return ios_; | |
66 | } | |
67 | ||
68 | /** Run a function in a coroutine. | |
69 | ||
70 | This call will block until the coroutine terminates. | |
71 | ||
72 | Function will be called with this signature: | |
73 | ||
74 | @code | |
75 | void f(args..., yield_context); | |
76 | @endcode | |
77 | ||
78 | @param f The Callable object to invoke. | |
79 | ||
80 | @param args Optional arguments forwarded to the callable object. | |
81 | */ | |
82 | #if BEAST_DOXYGEN | |
83 | template<class F, class... Args> | |
84 | void | |
85 | yield_to(F&& f, Args&&... args); | |
86 | #else | |
87 | template<class F> | |
88 | void | |
89 | yield_to(F&& f); | |
90 | ||
91 | template<class Function, class Arg, class... Args> | |
92 | void | |
93 | yield_to(Function&& f, Arg&& arg, Args&&... args) | |
94 | { | |
95 | yield_to(std::bind(f, | |
96 | std::forward<Arg>(arg), | |
97 | std::forward<Args>(args)..., | |
98 | std::placeholders::_1)); | |
99 | } | |
100 | #endif | |
101 | }; | |
102 | ||
103 | template<class Function> | |
104 | void | |
105 | enable_yield_to::yield_to(Function&& f) | |
106 | { | |
107 | { | |
108 | std::lock_guard<std::mutex> lock(m_); | |
109 | running_ = true; | |
110 | } | |
111 | boost::asio::spawn(ios_, | |
112 | [&](boost::asio::yield_context do_yield) | |
113 | { | |
114 | f(do_yield); | |
115 | std::lock_guard<std::mutex> lock(m_); | |
116 | running_ = false; | |
117 | cv_.notify_all(); | |
118 | } | |
119 | , boost::coroutines::attributes(2 * 1024 * 1024)); | |
120 | ||
121 | std::unique_lock<std::mutex> lock(m_); | |
122 | cv_.wait(lock, [&]{ return ! running_; }); | |
123 | } | |
124 | ||
125 | } // test | |
126 | } // beast | |
127 | ||
128 | #endif |