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