1 
2 //          Copyright Oliver Kowalke 2013.
3 // Distributed under the Boost Software License, Version 1.0.
4 //    (See accompanying file LICENSE_1_0.txt or copy at
5 //          http://www.boost.org/LICENSE_1_0.txt)
6 
7 #ifndef BOOST_FIBERS_CONDITION_VARIABLE_H
8 #define BOOST_FIBERS_CONDITION_VARIABLE_H
9 
10 #include <algorithm>
11 #include <atomic>
12 #include <chrono>
13 #include <functional>
14 #include <mutex>
15 
16 #include <boost/assert.hpp>
17 #include <boost/config.hpp>
18 
19 #include <boost/fiber/context.hpp>
20 #include <boost/fiber/detail/config.hpp>
21 #include <boost/fiber/detail/convert.hpp>
22 #include <boost/fiber/detail/spinlock.hpp>
23 #include <boost/fiber/exceptions.hpp>
24 #include <boost/fiber/mutex.hpp>
25 #include <boost/fiber/operations.hpp>
26 
27 #ifdef BOOST_HAS_ABI_HEADERS
28 #  include BOOST_ABI_PREFIX
29 #endif
30 
31 #ifdef _MSC_VER
32 # pragma warning(push)
33 //# pragma warning(disable:4251)
34 #endif
35 
36 namespace boost {
37 namespace fibers {
38 
39 enum class cv_status {
40     no_timeout = 1,
41     timeout
42 };
43 
44 class BOOST_FIBERS_DECL condition_variable_any {
45 private:
46     typedef context::wait_queue_t   wait_queue_t;
47 
48     wait_queue_t        wait_queue_{};
49     detail::spinlock    wait_queue_splk_{};
50 
51 public:
52     condition_variable_any() = default;
53 
~condition_variable_any()54     ~condition_variable_any() {
55         BOOST_ASSERT( wait_queue_.empty() );
56     }
57 
58     condition_variable_any( condition_variable_any const&) = delete;
59     condition_variable_any & operator=( condition_variable_any const&) = delete;
60 
61     void notify_one() noexcept;
62 
63     void notify_all() noexcept;
64 
65     template< typename LockType >
wait(LockType & lt)66     void wait( LockType & lt) {
67         context * ctx = context::active();
68         // atomically call lt.unlock() and block on *this
69         // store this fiber in waiting-queue
70         detail::spinlock_lock lk( wait_queue_splk_);
71         BOOST_ASSERT( ! ctx->wait_is_linked() );
72         ctx->wait_link( wait_queue_);
73         // unlock external lt
74         lt.unlock();
75         // suspend this fiber
76         ctx->suspend( lk);
77         // relock local lk
78         lk.lock();
79         // remove from waiting-queue
80         ctx->wait_unlink();
81         // unlock local lk
82         lk.unlock();
83         // relock external again before returning
84         try {
85             lt.lock();
86         } catch (...) {
87             std::terminate();
88         }
89         // post-conditions
90         BOOST_ASSERT( ! ctx->wait_is_linked() );
91     }
92 
93     template< typename LockType, typename Pred >
wait(LockType & lt,Pred pred)94     void wait( LockType & lt, Pred pred) {
95         while ( ! pred() ) {
96             wait( lt);
97         }
98     }
99 
100     template< typename LockType, typename Clock, typename Duration >
wait_until(LockType & lt,std::chrono::time_point<Clock,Duration> const & timeout_time_)101     cv_status wait_until( LockType & lt, std::chrono::time_point< Clock, Duration > const& timeout_time_) {
102         cv_status status = cv_status::no_timeout;
103         std::chrono::steady_clock::time_point timeout_time(
104                 detail::convert( timeout_time_) );
105         context * ctx = context::active();
106         // atomically call lt.unlock() and block on *this
107         // store this fiber in waiting-queue
108         detail::spinlock_lock lk( wait_queue_splk_);
109         BOOST_ASSERT( ! ctx->wait_is_linked() );
110         ctx->wait_link( wait_queue_);
111         // unlock external lt
112         lt.unlock();
113         // suspend this fiber
114         if ( ! ctx->wait_until( timeout_time, lk) ) {
115             status = cv_status::timeout;
116         }
117         // relock local lk
118         lk.lock();
119         // remove from waiting-queue
120         ctx->wait_unlink();
121         // unlock local lk
122         lk.unlock();
123         // relock external again before returning
124         try {
125             lt.lock();
126         } catch (...) {
127             std::terminate();
128         }
129         // post-conditions
130         BOOST_ASSERT( ! ctx->wait_is_linked() );
131         return status;
132     }
133 
134     template< typename LockType, typename Clock, typename Duration, typename Pred >
wait_until(LockType & lt,std::chrono::time_point<Clock,Duration> const & timeout_time,Pred pred)135     bool wait_until( LockType & lt,
136                      std::chrono::time_point< Clock, Duration > const& timeout_time, Pred pred) {
137         while ( ! pred() ) {
138             if ( cv_status::timeout == wait_until( lt, timeout_time) ) {
139                 return pred();
140             }
141         }
142         return true;
143     }
144 
145     template< typename LockType, typename Rep, typename Period >
wait_for(LockType & lt,std::chrono::duration<Rep,Period> const & timeout_duration)146     cv_status wait_for( LockType & lt, std::chrono::duration< Rep, Period > const& timeout_duration) {
147         return wait_until( lt,
148                            std::chrono::steady_clock::now() + timeout_duration);
149     }
150 
151     template< typename LockType, typename Rep, typename Period, typename Pred >
wait_for(LockType & lt,std::chrono::duration<Rep,Period> const & timeout_duration,Pred pred)152     bool wait_for( LockType & lt, std::chrono::duration< Rep, Period > const& timeout_duration, Pred pred) {
153         return wait_until( lt,
154                            std::chrono::steady_clock::now() + timeout_duration,
155                            pred);
156     }
157 };
158 
159 class BOOST_FIBERS_DECL condition_variable {
160 private:
161     condition_variable_any      cnd_;
162 
163 public:
164     condition_variable() = default;
165 
166     condition_variable( condition_variable const&) = delete;
167     condition_variable & operator=( condition_variable const&) = delete;
168 
notify_one()169     void notify_one() noexcept {
170         cnd_.notify_one();
171     }
172 
notify_all()173     void notify_all() noexcept {
174         cnd_.notify_all();
175     }
176 
wait(std::unique_lock<mutex> & lt)177     void wait( std::unique_lock< mutex > & lt) {
178         // pre-condition
179         BOOST_ASSERT( lt.owns_lock() );
180         BOOST_ASSERT( context::active() == lt.mutex()->owner_);
181         cnd_.wait( lt);
182         // post-condition
183         BOOST_ASSERT( lt.owns_lock() );
184         BOOST_ASSERT( context::active() == lt.mutex()->owner_);
185     }
186 
187     template< typename Pred >
wait(std::unique_lock<mutex> & lt,Pred pred)188     void wait( std::unique_lock< mutex > & lt, Pred pred) {
189         // pre-condition
190         BOOST_ASSERT( lt.owns_lock() );
191         BOOST_ASSERT( context::active() == lt.mutex()->owner_);
192         cnd_.wait( lt, pred);
193         // post-condition
194         BOOST_ASSERT( lt.owns_lock() );
195         BOOST_ASSERT( context::active() == lt.mutex()->owner_);
196     }
197 
198     template< typename Clock, typename Duration >
wait_until(std::unique_lock<mutex> & lt,std::chrono::time_point<Clock,Duration> const & timeout_time)199     cv_status wait_until( std::unique_lock< mutex > & lt,
200                           std::chrono::time_point< Clock, Duration > const& timeout_time) {
201         // pre-condition
202         BOOST_ASSERT( lt.owns_lock() );
203         BOOST_ASSERT( context::active() == lt.mutex()->owner_);
204         cv_status result = cnd_.wait_until( lt, timeout_time);
205         // post-condition
206         BOOST_ASSERT( lt.owns_lock() );
207         BOOST_ASSERT( context::active() == lt.mutex()->owner_);
208         return result;
209     }
210 
211     template< typename Clock, typename Duration, typename Pred >
wait_until(std::unique_lock<mutex> & lt,std::chrono::time_point<Clock,Duration> const & timeout_time,Pred pred)212     bool wait_until( std::unique_lock< mutex > & lt,
213                      std::chrono::time_point< Clock, Duration > const& timeout_time, Pred pred) {
214         // pre-condition
215         BOOST_ASSERT( lt.owns_lock() );
216         BOOST_ASSERT( context::active() == lt.mutex()->owner_);
217         bool result = cnd_.wait_until( lt, timeout_time, pred);
218         // post-condition
219         BOOST_ASSERT( lt.owns_lock() );
220         BOOST_ASSERT( context::active() == lt.mutex()->owner_);
221         return result;
222     }
223 
224     template< typename Rep, typename Period >
wait_for(std::unique_lock<mutex> & lt,std::chrono::duration<Rep,Period> const & timeout_duration)225     cv_status wait_for( std::unique_lock< mutex > & lt,
226                         std::chrono::duration< Rep, Period > const& timeout_duration) {
227         // pre-condition
228         BOOST_ASSERT( lt.owns_lock() );
229         BOOST_ASSERT( context::active() == lt.mutex()->owner_);
230         cv_status result = cnd_.wait_for( lt, timeout_duration);
231         // post-condition
232         BOOST_ASSERT( lt.owns_lock() );
233         BOOST_ASSERT( context::active() == lt.mutex()->owner_);
234         return result;
235     }
236 
237     template< typename Rep, typename Period, typename Pred >
wait_for(std::unique_lock<mutex> & lt,std::chrono::duration<Rep,Period> const & timeout_duration,Pred pred)238     bool wait_for( std::unique_lock< mutex > & lt,
239                    std::chrono::duration< Rep, Period > const& timeout_duration, Pred pred) {
240         // pre-condition
241         BOOST_ASSERT( lt.owns_lock() );
242         BOOST_ASSERT( context::active() == lt.mutex()->owner_);
243         bool result = cnd_.wait_for( lt, timeout_duration, pred);
244         // post-condition
245         BOOST_ASSERT( lt.owns_lock() );
246         BOOST_ASSERT( context::active() == lt.mutex()->owner_);
247         return result;
248     }
249 };
250 
251 }}
252 
253 #ifdef _MSC_VER
254 # pragma warning(pop)
255 #endif
256 
257 #ifdef BOOST_HAS_ABI_HEADERS
258 #  include BOOST_ABI_SUFFIX
259 #endif
260 
261 #endif // BOOST_FIBERS_CONDITION_VARIABLE_H
262