HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
UT_TaskLock.h
Go to the documentation of this file.
1 /*
2  * PROPRIETARY INFORMATION. This software is proprietary to
3  * Side Effects Software Inc., and is not to be reproduced,
4  * transmitted, or disclosed in any way without written permission.
5  *
6  * NAME: UT_TaskLock.h (UT Library, C++)
7  *
8  * COMMENTS:
9  *
10  */
11 
12 #ifndef __UT_TASKLOCK_H_INCLUDED__
13 #define __UT_TASKLOCK_H_INCLUDED__
14 
15 #include "UT_API.h"
16 #include "UT_Assert.h"
17 #include "UT_Debug.h"
18 #include "UT_NonCopyable.h"
19 #include "UT_Array.h"
20 #include "UT_TaskArena.h"
21 #include "UT_TaskScope.h"
22 #include <SYS/SYS_BoostThread.h>
23 
24 #include <stddef.h>
25 
26 // Set the following line to #if 1 to enable debug message output
27 #ifndef UT_TASKLOCK_DBG
28  #if 0
29  #define UT_TASKLOCK_DBG(ZZ) UT_DBGOUT(ZZ)
30  #else
31  #define UT_TASKLOCK_DBG(ZZ)
32  #endif
33 #endif
34 
35 /// A recursive mutex class for synchronizing amongst tasks defined by
36 /// UT_TaskScope.
37 ///
38 /// The assumptions that UT_TaskScope uses for tasks are:
39 /// - Tasks are hierarchical in a forest of trees
40 /// - When child tasks are running, the parent task is not
41 /// - Child tasks always finish before their parent task
42 ///
43 /// Requirements:
44 /// - You must unlock before your own task ends
45 /// - You must unlock your lock before waiting for a child task to end
46 ///
47 template <bool NESTED>
49 {
50 private:
51  friend class Scope;
52 
53  typedef hboost::mutex ut_Mutex;
54  typedef ut_Mutex::scoped_lock ut_MutexLock;
55  typedef hboost::condition_variable ut_Condition;
56 
57  struct ut_Data
58  {
59  const UT_TaskScope *myOwner;
60  int myLockCount;
61 
62  ut_Data()
63  : myOwner(NULL)
64  , myLockCount(0)
65  {
66  }
67  };
68 
69  ut_Mutex myMutex;
70  ut_Condition myCondition;
71  int myNumWaitingThreads;
72  ut_Data myData;
73  UT_Array<ut_Data> myDataStack;
74 
75  bool unsafeTryLock(const UT_TaskScope &task, bool &was_first)
76  {
77  UT_ASSERT(myData.myLockCount >= 0);
78  if (myData.myLockCount == 0 || myData.myOwner == &task)
79  {
80  UT_ASSERT(myData.myLockCount > 0 || myData.myOwner == NULL);
81  myDataStack.append(myData);
82  myData.myOwner = &task;
83  ++myData.myLockCount;
84  was_first = (myData.myLockCount == 1);
85  UT_TASKLOCK_DBG(("Acquired first lock %p for owner %p (count %d)",
86  this, myData.myOwner, myData.myLockCount));
87  return true;
88  }
89  UT_TASKLOCK_DBG(("Failed fast lock %p for task %p, owner %p "
90  "(count %d), waiting threads %d",
91  this, &task, myData.myOwner, myData.myLockCount,
92  myNumWaitingThreads));
93  return false;
94  }
95 
96  // Returns true if locked within timeout.
97  bool privateLock(hboost::system_time const &wait_until, bool &was_first)
98  {
99  ut_MutexLock lock_scope(myMutex);
100  const UT_TaskScope & task = getTaskScope();
101 
102  // If nobody holds the lock then acquire it right away
103  if (unsafeTryLock(task, was_first))
104  return true;
105  // If we have positive lock count, then we should already have an owner
106  UT_ASSERT(myData.myOwner != NULL);
107 
108  // Perform lock, blocking if needed
109  bool ok = true;
110  while (ok)
111  {
112  // Test if we can acquire the lock
113  if (myData.myOwner == NULL
114  || task.isAncestor(*myData.myOwner))
115  {
116  UT_ASSERT(myData.myOwner != NULL || myData.myLockCount == 0);
117  myDataStack.append(myData);
118  myData.myOwner = &task;
119  myData.myLockCount = 1;
120  was_first = true;
121  UT_TASKLOCK_DBG(("Took lock %p for owner %p from"
122  " %p (count %d)",
123  this, myData.myOwner,
124  myDataStack.last().myOwner,
125  myDataStack.last().myLockCount));
126  return true;
127  }
128 
129  // unlock myMutex and wait until it can be acquired again
130  UT_TASKLOCK_DBG(("Waiting on lock %p with owner %p (count %d) "
131  "prev waiting threads %d",
132  this, myData.myOwner, myData.myLockCount,
133  myNumWaitingThreads));
134  ++myNumWaitingThreads;
135  if (wait_until.is_pos_infinity())
136  {
137  myCondition.wait(lock_scope);
138  }
139  else
140  {
141  ok = myCondition.timed_wait(lock_scope, wait_until);
142  }
143  --myNumWaitingThreads;
144  UT_ASSERT(myNumWaitingThreads >= 0);
145  }
146 
147  return false;
148  }
149 
150  bool privateTryLock()
151  {
152  ut_MutexLock lock_scope(myMutex);
153  const UT_TaskScope & task = getTaskScope();
154  bool was_first = false;
155 
156  return unsafeTryLock(task, was_first);
157  }
158 
159  void privateUnlock()
160  {
161  ut_MutexLock lock_scope(myMutex);
162  bool notify;
163 
164  UT_TASKLOCK_DBG(("Release lock %p for owner %p, "
165  "new owner %p (count %d), waiting threads %d",
166  this, myData.myOwner,
167  myDataStack.last().myOwner,
168  myDataStack.last().myLockCount,
169  myNumWaitingThreads));
170 
171  UT_ASSERT(myData.myLockCount >= 1);
172  notify = (myData.myLockCount == 1);
173 
174  myData = myDataStack.last();
175  myDataStack.removeLast();
176 
177  // Release the lock if the count goes down to zero.
178  UT_ASSERT(myData.myLockCount >= 0);
179  if (notify)
180  {
181  UT_ASSERT(myData.myLockCount > 0 || myData.myOwner == NULL);
182 
183  // Signal all the threads that are waiting. We don't want to
184  // signal just one, since the next one wouldn't get signalled until
185  // unlock() is called again, and we want to let in all descendent
186  // tasks
187  if (myNumWaitingThreads > 0)
188  {
189  myCondition.notify_all();
190  }
191  }
192  }
193 
194  bool privateHasLock()
195  {
196  ut_MutexLock lock_scope(myMutex);
197  const UT_TaskScope & task = getTaskScope();
198  return (myData.myLockCount > 0 && myData.myOwner == &task);
199  }
200 
201  static const UT_TaskScope &getTaskScope()
202  {
203  if (NESTED)
205  else
207  }
208 
209 public:
210 
212  : myNumWaitingThreads(0)
213  {
214  }
216  {
217  }
218 
219  void lock()
220  {
221  bool was_first = false;
222  (void) privateLock(hboost::system_time(hboost::posix_time::pos_infin),
223  was_first);
224  }
225 
226  /// Same as lock() except it also returns if it was the first time this
227  /// task scope obtained the lock (ie. non-recursively).
228  void lock(bool &was_first)
229  {
230  (void) privateLock(hboost::system_time(hboost::posix_time::pos_infin),
231  was_first);
232  }
233 
234  bool timedLock(int timeout)
235  {
236  bool was_first = false;
237  return privateLock(hboost::get_system_time()
238  + hboost::posix_time::milliseconds(timeout),
239  was_first);
240  }
241 
242  bool tryLock()
243  {
244  return privateTryLock();
245  }
246 
247  bool safeLock()
248  {
249  lock();
250  return true;
251  }
252 
253  void unlock()
254  {
255  privateUnlock();
256  }
257 
258  bool hasLock()
259  {
260  return privateHasLock();
261  }
262 
263  /// Class for auto-unlocking
265 };
266 
267 /// A recursive mutex class for synchronizing amongst tasks defined by
268 /// UT_TaskScope.
269 ///
270 /// The assumptions that UT_TaskScope uses for tasks are:
271 /// - Tasks are hierarchical in a forest of trees
272 /// - When child tasks are running, the parent task is not
273 /// - Child tasks always finish before their parent task
274 ///
275 /// Requirements:
276 /// - You must unlock before your own task ends
277 /// - You must unlock your lock before waiting for a child task to end
278 ///
280 
282 
283 
284 /// UT_TaskLock that avoids deadlocks when used with TBB task scheduling.
285 class UT_API UT_TaskLockWithArena : private UT_TaskLock
286 {
287 public:
288 
289  /// Performs the functor F while inside this lock scope in UT_TaskArena.
290  /// This method allows the optimization that if we're calling this while
291  /// the lock is already held in the same task scope, then we can avoid
292  /// creating an unnecessary task arena.
293  template <typename F>
294  void
295  lockedExecute(const F &functor)
296  {
297  bool was_first = false;
298  (void) UT_TaskLock::lock(was_first);
299  if (!was_first)
300  {
301  functor();
302  }
303  else
304  {
305  UT_TaskArena arena;
306  arena.execute(functor);
307  }
309  }
310 
311  /// Performs the functor F while inside this lock scope OUTSIDE an arena.
312  /// @note Only do this if you know functor will never spawn tasks!
313  template <typename F>
314  void
315  lockedExecuteWithoutArena(const F &functor)
316  {
318  functor();
320  }
321 
322  using UT_TaskLock::hasLock;
323 };
324 
325 #endif // __UT_TASKLOCK_H_INCLUDED__
T & last()
Definition: UT_Array.h:591
#define UT_TASKLOCK_DBG(ZZ)
Definition: UT_TaskLock.h:31
void execute(F &functor)
Definition: UT_TaskArena.h:37
static const UT_TaskScope & getOrCreateCurrent()
Definition: UT_TaskScope.h:111
bool tryLock()
Definition: UT_TaskLock.h:242
SYS_FORCE_INLINE void removeLast()
Definition: UT_Array.h:223
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
#define UT_API
Definition: UT_API.h:13
static const UT_TaskScope & getOrCreateRoot()
Definition: UT_TaskScope.h:133
UT_TaskLockT< false > UT_TaskRootLock
Definition: UT_TaskLock.h:281
bool safeLock()
Definition: UT_TaskLock.h:247
GLbitfield GLuint64 timeout
Definition: glcorearb.h:1598
bool timedLock(int timeout)
Definition: UT_TaskLock.h:234
bool hasLock()
Definition: UT_TaskLock.h:258
void lock(bool &was_first)
Definition: UT_TaskLock.h:228
bool isAncestor(const UT_TaskScope &parent) const
Test if the given task scope is an ancestor of ours.
Definition: UT_TaskScope.h:140
UT_TaskLockT< true > UT_TaskLock
Definition: UT_TaskLock.h:279
UT_TaskLock that avoids deadlocks when used with TBB task scheduling.
Definition: UT_TaskLock.h:285
exint append(void)
Definition: UT_Array.h:95
void lockedExecuteWithoutArena(const F &functor)
Definition: UT_TaskLock.h:315
#define UT_ASSERT(ZZ)
Definition: UT_Assert.h:126
void unlock()
Definition: UT_TaskLock.h:253
void lockedExecute(const F &functor)
Definition: UT_TaskLock.h:295
UT_LockScopeType< UT_TaskLockT< NESTED > > Scope
Class for auto-unlocking.
Definition: UT_TaskLock.h:264