• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #ifndef BOOST_BASIC_TIMED_MUTEX_WIN32_HPP
2 #define BOOST_BASIC_TIMED_MUTEX_WIN32_HPP
3 
4 //  basic_timed_mutex_win32.hpp
5 //
6 //  (C) Copyright 2006-8 Anthony Williams
7 //  (C) Copyright 2011-2012 Vicente J. Botet Escriba
8 //
9 //  Distributed under the Boost Software License, Version 1.0. (See
10 //  accompanying file LICENSE_1_0.txt or copy at
11 //  http://www.boost.org/LICENSE_1_0.txt)
12 
13 #include <boost/assert.hpp>
14 #include <boost/thread/win32/thread_primitives.hpp>
15 #include <boost/thread/win32/interlocked_read.hpp>
16 #include <boost/thread/thread_time.hpp>
17 #if defined BOOST_THREAD_USES_DATETIME
18 #include <boost/thread/xtime.hpp>
19 #endif
20 #include <boost/detail/interlocked.hpp>
21 #ifdef BOOST_THREAD_USES_CHRONO
22 #include <boost/chrono/system_clocks.hpp>
23 #include <boost/chrono/ceil.hpp>
24 #endif
25 #include <boost/thread/detail/platform_time.hpp>
26 
27 #include <boost/config/abi_prefix.hpp>
28 
29 namespace boost
30 {
31     namespace detail
32     {
33         struct basic_timed_mutex
34         {
35             BOOST_STATIC_CONSTANT(unsigned char,lock_flag_bit=31);
36             BOOST_STATIC_CONSTANT(unsigned char,event_set_flag_bit=30);
37             BOOST_STATIC_CONSTANT(long,lock_flag_value=1<<lock_flag_bit);
38             BOOST_STATIC_CONSTANT(long,event_set_flag_value=1<<event_set_flag_bit);
39             long active_count;
40             void* event;
41 
initializeboost::detail::basic_timed_mutex42             void initialize()
43             {
44                 active_count=0;
45                 event=0;
46             }
47 
destroyboost::detail::basic_timed_mutex48             void destroy()
49             {
50 #ifdef BOOST_MSVC
51 #pragma warning(push)
52 #pragma warning(disable:4312)
53 #endif
54                 void* const old_event=BOOST_INTERLOCKED_EXCHANGE_POINTER(&event,0);
55 #ifdef BOOST_MSVC
56 #pragma warning(pop)
57 #endif
58                 if(old_event)
59                 {
60                     winapi::CloseHandle(old_event);
61                 }
62             }
63 
64             // Take the lock flag if it's available
try_lockboost::detail::basic_timed_mutex65             bool try_lock() BOOST_NOEXCEPT
66             {
67                 return !win32::interlocked_bit_test_and_set(&active_count,lock_flag_bit);
68             }
69 
lockboost::detail::basic_timed_mutex70             void lock()
71             {
72                 if(try_lock())
73                 {
74                     return;
75                 }
76                 long old_count=active_count;
77                 mark_waiting_and_try_lock(old_count);
78 
79                 if(old_count&lock_flag_value)
80                 {
81                     void* const sem=get_event();
82 
83                     do
84                     {
85                         if(winapi::WaitForSingleObjectEx(sem,::boost::detail::win32::infinite,0)==0)
86                         {
87                             clear_waiting_and_try_lock(old_count);
88                         }
89                     }
90                     while(old_count&lock_flag_value);
91                 }
92             }
93 
94             // Loop until the number of waiters has been incremented or we've taken the lock flag
95             // The loop is necessary since this function may be called by multiple threads simultaneously
mark_waiting_and_try_lockboost::detail::basic_timed_mutex96             void mark_waiting_and_try_lock(long& old_count)
97             {
98                 for(;;)
99                 {
100                     bool const was_locked=(old_count&lock_flag_value) ? true : false;
101                     long const new_count=was_locked?(old_count+1):(old_count|lock_flag_value);
102                     long const current=BOOST_INTERLOCKED_COMPARE_EXCHANGE(&active_count,new_count,old_count);
103                     if(current==old_count)
104                     {
105                         if(was_locked)
106                             old_count=new_count;
107                         // else we've taken the lock flag
108                             // don't update old_count so that the calling function can see that
109                             // the old lock flag was 0 and know that we've taken the lock flag
110                         break;
111                     }
112                     old_count=current;
113                 }
114             }
115 
116             // Loop until someone else has taken the lock flag and cleared the event set flag or
117             // until we've taken the lock flag and cleared the event set flag and decremented the
118             // number of waiters
119             // The loop is necessary since this function may be called by multiple threads simultaneously
clear_waiting_and_try_lockboost::detail::basic_timed_mutex120             void clear_waiting_and_try_lock(long& old_count)
121             {
122                 old_count&=~lock_flag_value;
123                 old_count|=event_set_flag_value;
124                 for(;;)
125                 {
126                     long const new_count=((old_count&lock_flag_value)?old_count:((old_count-1)|lock_flag_value))&~event_set_flag_value;
127                     long const current=BOOST_INTERLOCKED_COMPARE_EXCHANGE(&active_count,new_count,old_count);
128                     if(current==old_count)
129                     {
130                         // if someone else has taken the lock flag
131                             // no need to update old_count since old_count == new_count (ignoring
132                             // event_set_flag_value which the calling function doesn't care about)
133                         // else we've taken the lock flag
134                             // don't update old_count so that the calling function can see that
135                             // the old lock flag was 0 and know that we've taken the lock flag
136                         break;
137                     }
138                     old_count=current;
139                 }
140             }
141 
142         private:
getMsboost::detail::basic_timed_mutex143             unsigned long getMs(detail::platform_duration const& d)
144             {
145                 return static_cast<unsigned long>(d.getMs());
146             }
147 
148             template <typename Duration>
getMsboost::detail::basic_timed_mutex149             unsigned long getMs(Duration const& d)
150             {
151                 return static_cast<unsigned long>(chrono::ceil<chrono::milliseconds>(d).count());
152             }
153 
154             template <typename Clock, typename Timepoint, typename Duration>
do_lock_untilboost::detail::basic_timed_mutex155             bool do_lock_until(Timepoint const& t, Duration const& max)
156             {
157                 if(try_lock())
158                 {
159                     return true;
160                 }
161 
162                 long old_count=active_count;
163                 mark_waiting_and_try_lock(old_count);
164 
165                 if(old_count&lock_flag_value)
166                 {
167                     void* const sem=get_event();
168 
169                     // If the clock is the system clock, it may jump while this function
170                     // is waiting. To compensate for this and time out near the correct
171                     // time, we call WaitForSingleObjectEx() in a loop with a short
172                     // timeout and recheck the time remaining each time through the loop.
173                     do
174                     {
175                         Duration d(t - Clock::now());
176                         if(d <= Duration::zero()) // timeout occurred
177                         {
178                             BOOST_INTERLOCKED_DECREMENT(&active_count);
179                             return false;
180                         }
181                         if(max != Duration::zero())
182                         {
183                             d = (std::min)(d, max);
184                         }
185                         if(winapi::WaitForSingleObjectEx(sem,getMs(d),0)==0)
186                         {
187                             clear_waiting_and_try_lock(old_count);
188                         }
189                     }
190                     while(old_count&lock_flag_value);
191                 }
192                 return true;
193             }
194         public:
195 
196 #if defined BOOST_THREAD_USES_DATETIME
timed_lockboost::detail::basic_timed_mutex197             bool timed_lock(::boost::system_time const& wait_until)
198             {
199                 const detail::real_platform_timepoint t(wait_until);
200                 return do_lock_until<detail::real_platform_clock>(t, detail::platform_milliseconds(BOOST_THREAD_POLL_INTERVAL_MILLISECONDS));
201             }
202 
203             template<typename Duration>
timed_lockboost::detail::basic_timed_mutex204             bool timed_lock(Duration const& timeout)
205             {
206                 const detail::mono_platform_timepoint t(detail::mono_platform_clock::now() + detail::platform_duration(timeout));
207                 // The reference clock is steady and so no need to poll periodically, thus 0 ms max (i.e. no max)
208                 return do_lock_until<detail::mono_platform_clock>(t, detail::platform_duration::zero());
209             }
210 
timed_lockboost::detail::basic_timed_mutex211             bool timed_lock(boost::xtime const& timeout)
212             {
213                 return timed_lock(boost::system_time(timeout));
214             }
215 #endif
216 #ifdef BOOST_THREAD_USES_CHRONO
217             template <class Rep, class Period>
try_lock_forboost::detail::basic_timed_mutex218             bool try_lock_for(const chrono::duration<Rep, Period>& rel_time)
219             {
220                 const chrono::steady_clock::time_point t(chrono::steady_clock::now() + rel_time);
221                 typedef typename chrono::duration<Rep, Period> Duration;
222                 typedef typename common_type<Duration, typename chrono::steady_clock::duration>::type common_duration;
223                 // The reference clock is steady and so no need to poll periodically, thus 0 ms max (i.e. no max)
224                 return do_lock_until<chrono::steady_clock>(t, common_duration::zero());
225             }
226             template <class Duration>
try_lock_untilboost::detail::basic_timed_mutex227             bool try_lock_until(const chrono::time_point<chrono::steady_clock, Duration>& t)
228             {
229                 typedef typename common_type<Duration, typename chrono::steady_clock::duration>::type common_duration;
230                 // The reference clock is steady and so no need to poll periodically, thus 0 ms max (i.e. no max)
231                 return do_lock_until<chrono::steady_clock>(t, common_duration::zero());
232             }
233             template <class Clock, class Duration>
try_lock_untilboost::detail::basic_timed_mutex234             bool try_lock_until(const chrono::time_point<Clock, Duration>& t)
235             {
236                 typedef typename common_type<Duration, typename Clock::duration>::type common_duration;
237                 return do_lock_until<Clock>(t, common_duration(chrono::milliseconds(BOOST_THREAD_POLL_INTERVAL_MILLISECONDS)));
238             }
239 #endif
240 
unlockboost::detail::basic_timed_mutex241             void unlock()
242             {
243                 // Clear the lock flag using atomic addition (works since long is always 32 bits on Windows)
244                 long const old_count=BOOST_INTERLOCKED_EXCHANGE_ADD(&active_count,lock_flag_value);
245                 // If someone is waiting to take the lock, set the event set flag and, if
246                 // the event set flag hadn't already been set, send an event.
247                 if(!(old_count&event_set_flag_value) && (old_count>lock_flag_value))
248                 {
249                     if(!win32::interlocked_bit_test_and_set(&active_count,event_set_flag_bit))
250                     {
251                         winapi::SetEvent(get_event());
252                     }
253                 }
254             }
255 
256         private:
257             // Create an event in a thread-safe way
258             // The first thread to create the event wins and all other thread will use that event
get_eventboost::detail::basic_timed_mutex259             void* get_event()
260             {
261                 void* current_event=::boost::detail::interlocked_read_acquire(&event);
262 
263                 if(!current_event)
264                 {
265                     void* const new_event=win32::create_anonymous_event(win32::auto_reset_event,win32::event_initially_reset);
266 #ifdef BOOST_MSVC
267 #pragma warning(push)
268 #pragma warning(disable:4311)
269 #pragma warning(disable:4312)
270 #endif
271                     void* const old_event=BOOST_INTERLOCKED_COMPARE_EXCHANGE_POINTER(&event,new_event,0);
272 #ifdef BOOST_MSVC
273 #pragma warning(pop)
274 #endif
275                     if(old_event!=0)
276                     {
277                         winapi::CloseHandle(new_event);
278                         return old_event;
279                     }
280                     else
281                     {
282                         return new_event;
283                     }
284                 }
285                 return current_event;
286             }
287 
288         };
289 
290     }
291 }
292 
293 #define BOOST_BASIC_TIMED_MUTEX_INITIALIZER {0}
294 
295 #include <boost/config/abi_suffix.hpp>
296 
297 #endif
298