Geant4 Cross Reference

Cross-Referencing   Geant4
Geant4/externals/ptl/include/PTL/AutoLock.hh

Version: [ ReleaseNotes ] [ 1.0 ] [ 1.1 ] [ 2.0 ] [ 3.0 ] [ 3.1 ] [ 3.2 ] [ 4.0 ] [ 4.0.p1 ] [ 4.0.p2 ] [ 4.1 ] [ 4.1.p1 ] [ 5.0 ] [ 5.0.p1 ] [ 5.1 ] [ 5.1.p1 ] [ 5.2 ] [ 5.2.p1 ] [ 5.2.p2 ] [ 6.0 ] [ 6.0.p1 ] [ 6.1 ] [ 6.2 ] [ 6.2.p1 ] [ 6.2.p2 ] [ 7.0 ] [ 7.0.p1 ] [ 7.1 ] [ 7.1.p1 ] [ 8.0 ] [ 8.0.p1 ] [ 8.1 ] [ 8.1.p1 ] [ 8.1.p2 ] [ 8.2 ] [ 8.2.p1 ] [ 8.3 ] [ 8.3.p1 ] [ 8.3.p2 ] [ 9.0 ] [ 9.0.p1 ] [ 9.0.p2 ] [ 9.1 ] [ 9.1.p1 ] [ 9.1.p2 ] [ 9.1.p3 ] [ 9.2 ] [ 9.2.p1 ] [ 9.2.p2 ] [ 9.2.p3 ] [ 9.2.p4 ] [ 9.3 ] [ 9.3.p1 ] [ 9.3.p2 ] [ 9.4 ] [ 9.4.p1 ] [ 9.4.p2 ] [ 9.4.p3 ] [ 9.4.p4 ] [ 9.5 ] [ 9.5.p1 ] [ 9.5.p2 ] [ 9.6 ] [ 9.6.p1 ] [ 9.6.p2 ] [ 9.6.p3 ] [ 9.6.p4 ] [ 10.0 ] [ 10.0.p1 ] [ 10.0.p2 ] [ 10.0.p3 ] [ 10.0.p4 ] [ 10.1 ] [ 10.1.p1 ] [ 10.1.p2 ] [ 10.1.p3 ] [ 10.2 ] [ 10.2.p1 ] [ 10.2.p2 ] [ 10.2.p3 ] [ 10.3 ] [ 10.3.p1 ] [ 10.3.p2 ] [ 10.3.p3 ] [ 10.4 ] [ 10.4.p1 ] [ 10.4.p2 ] [ 10.4.p3 ] [ 10.5 ] [ 10.5.p1 ] [ 10.6 ] [ 10.6.p1 ] [ 10.6.p2 ] [ 10.6.p3 ] [ 10.7 ] [ 10.7.p1 ] [ 10.7.p2 ] [ 10.7.p3 ] [ 10.7.p4 ] [ 11.0 ] [ 11.0.p1 ] [ 11.0.p2 ] [ 11.0.p3, ] [ 11.0.p4 ] [ 11.1 ] [ 11.1.1 ] [ 11.1.2 ] [ 11.1.3 ] [ 11.2 ] [ 11.2.1 ] [ 11.2.2 ] [ 11.3.0 ]

  1 //
  2 // MIT License
  3 // Copyright (c) 2020 Jonathan R. Madsen
  4 // Permission is hereby granted, free of charge, to any person obtaining a copy
  5 // of this software and associated documentation files (the "Software"), to deal
  6 // in the Software without restriction, including without limitation the rights
  7 // to use, copy, modify, merge, publish, distribute, sublicense, and
  8 // copies of the Software, and to permit persons to whom the Software is
  9 // furnished to do so, subject to the following conditions:
 10 // The above copyright notice and this permission notice shall be included in
 11 // all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED
 12 // "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 13 // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 14 // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 15 // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 16 // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 17 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 18 //
 19 //
 20 // ---------------------------------------------------------------
 21 // Tasking class header file
 22 //
 23 /// Class Description:
 24 ///
 25 /// This class provides a mechanism to create a mutex and locks/unlocks it.
 26 /// Can be used by applications to implement in a portable way a mutexing logic.
 27 /// Usage Example:
 28 ///
 29 ///      #include "Threading.hh"
 30 ///      #include "AutoLock.hh"
 31 ///
 32 ///      /// defined somewhere -- static so all threads see the same mutex
 33 ///      static Mutex aMutex;
 34 ///
 35 ///      /// somewhere else:
 36 ///      /// The AutoLock instance will automatically unlock the mutex when it
 37 ///      /// goes out of scope. One typically defines the scope within { } if
 38 ///      /// there is thread-safe code following the auto-lock
 39 ///
 40 ///      {
 41 ///          AutoLock l(&aMutex);
 42 ///          ProtectedCode();
 43 ///      }
 44 ///
 45 ///      UnprotectedCode();
 46 ///
 47 ///      /// When ProtectedCode() is calling a function that also tries to lock
 48 ///      /// a normal AutoLock + Mutex will "deadlock". In other words, the
 49 ///      /// the mutex in the ProtectedCode() function will wait forever to
 50 ///      /// acquire the lock that is being held by the function that called
 51 ///      /// ProtectedCode(). In this situation, use a RecursiveAutoLock +
 52 ///      /// RecursiveMutex, e.g.
 53 ///
 54 ///      /// defined somewhere -- static so all threads see the same mutex
 55 ///      static RecursiveMutex aRecursiveMutex;
 56 ///
 57 ///      /// this function is sometimes called directly and sometimes called
 58 ///      /// from SomeFunction_B(), which also locks the mutex
 59 ///      void SomeFunction_A()
 60 ///      {
 61 ///          /// when called from SomeFunction_B(), a Mutex + AutoLock will
 62 ///          /// deadlock
 63 ///          RecursiveAutoLock l(&aRecursiveMutex);
 64 ///          /// do something
 65 ///      }
 66 ///
 67 ///      void SomeFunction_B()
 68 ///      {
 69 ///
 70 ///          {
 71 ///              RecursiveAutoLock l(&aRecursiveMutex);
 72 ///              SomeFunction_A();
 73 ///          }
 74 ///
 75 ///          UnprotectedCode();
 76 ///      }
 77 ///
 78 ///
 79 /// ---------------------------------------------------------------
 80 /// Author: Andrea Dotti (15 Feb 2013): First Implementation
 81 ///
 82 /// Update: Jonathan Madsen (9 Feb 2018): Replaced custom implementation
 83 ///      with inheritance from C++11 unique_lock, which inherits the
 84 ///      following member functions:
 85 ///
 86 ///      - unique_lock(unique_lock&& other) noexcept;
 87 ///      - explicit unique_lock(mutex_type& m);
 88 ///      - unique_lock(mutex_type& m, std::defer_lock_t t) noexcept;
 89 ///      - unique_lock(mutex_type& m, std::try_to_lock_t t);
 90 ///      - unique_lock(mutex_type& m, std::adopt_lock_t t);
 91 ///
 92 ///      - template <typename Rep, typename Period>
 93 ///        unique_lock(mutex_type& m,
 94 ///                   const std::chrono::duration<Rep,Period>&
 95 ///                   timeout_duration);
 96 ///
 97 ///      - template<typename Clock, typename Duration>
 98 ///        unique_lock(mutex_type& m,
 99 ///              const std::chrono::time_point<Clock,Duration>& timeout_time);
100 ///
101 ///      - void lock();
102 ///      - void unlock();
103 ///      - bool try_lock();
104 ///
105 ///      - template <typename Rep, typename Period>
106 ///        bool try_lock_for(const std::chrono::duration<Rep,Period>&);
107 ///
108 ///      - template <typename Rep, typename Period>
109 ///        bool try_lock_until(const std::chrono::time_point<Clock,Duration>&);
110 ///
111 ///      - void swap(unique_lock& other) noexcept;
112 ///      - mutex_type* release() noexcept;
113 ///      - mutex_type* mutex() const noexcept;
114 ///      - bool owns_lock() const noexcept;
115 ///      - explicit operator bool() const noexcept;
116 ///      - unique_lock& operator=(unique_lock&& other);
117 ///
118 /// ---------------------------------------------------------------
119 ///
120 /// Note that AutoLock is defined also for a sequential Tasking build but below
121 /// regarding implementation (also found in Threading.hh)
122 ///
123 ///
124 ///          NOTE ON Tasking SERIAL BUILDS AND MUTEX/UNIQUE_LOCK
125 ///          ==================================================
126 ///
127 /// Mutex and RecursiveMutex are always C++11 std::mutex types
128 /// however, in serial mode, using MUTEXLOCK and MUTEXUNLOCK on these
129 /// types has no effect -- i.e. the mutexes are not actually locked or unlocked
130 ///
131 /// Additionally, when a Mutex or RecursiveMutex is used with AutoLock
132 /// and RecursiveAutoLock, respectively, these classes also suppressing
133 /// the locking and unlocking of the mutex. Regardless of the build type,
134 /// AutoLock and RecursiveAutoLock inherit from std::unique_lock<std::mutex>
135 /// and std::unique_lock<std::recursive_mutex>, respectively. This means
136 /// that in situations (such as is needed by the analysis category), the
137 /// AutoLock and RecursiveAutoLock can be passed to functions requesting
138 /// a std::unique_lock. Within these functions, since std::unique_lock
139 /// member functions are not virtual, they will not retain the dummy locking
140 /// and unlocking behavior
141 /// --> An example of this behavior can be found below
142 ///
143 ///  Jonathan R. Madsen (February 21, 2018)
144 ///
145 /***
146 
147 //======================================================================================//
148 
149 typedef std::unique_lock<std::mutex> unique_lock_t;
150 // functions for casting AutoLock to std::unique_lock to demonstrate
151 // that AutoLock is NOT polymorphic
152 void as_unique_lock(unique_lock_t* lock) { lock->lock(); }
153 void as_unique_unlock(unique_lock_t* lock) { lock->unlock(); }
154 
155 //======================================================================================//
156 
157 void run(const uint64_t& n)
158 {
159     // sync the threads a bit
160     std::this_thread::sleep_for(std::chrono::milliseconds(10));
161 
162     // get two mutexes to avoid deadlock when l32 actually locks
163     AutoLock l32(TypeMutex<int32_t>(), std::defer_lock);
164     AutoLock l64(TypeMutex<int64_t>(), std::defer_lock);
165 
166     // when serial: will not execute std::unique_lock::lock() because
167     // it overrides the member function
168     l32.lock();
169     // regardless of serial or MT: will execute std::unique_lock::lock()
170     // because std::unique_lock::lock() is not virtual
171     as_unique_lock(&l64);
172 
173     std::cout << "Running iteration " << n << "..." << std::endl;
174 }
175 
176 //======================================================================================//
177 // execute some work
178 template <typename thread_type = std::thread>
179 void exec(uint64_t n)
180 {
181     // get two mutexes to avoid deadlock when l32 actually locks
182     AutoLock l32(TypeMutex<int32_t>(), std::defer_lock);
183     AutoLock l64(TypeMutex<int64_t>(), std::defer_lock);
184 
185     std::vector<thread_type*> threads(n, nullptr);
186     for(uint64_t i = 0; i < n; ++i)
187     {
188         threads[i] = new thread_type();
189         *(threads[i]) = std::move(thread_type(run, i));
190     }
191 
192     // when serial: will not execute std::unique_lock::lock() because
193     // it overrides the member function
194     l32.lock();
195     // regardless of serial or MT: will execute std::unique_lock::lock()
196     // because std::unique_lock::lock() is not virtual
197     as_unique_lock(&l64);
198 
199     std::cout << "Joining..." << std::endl;
200 
201     // when serial: will not execute std::unique_lock::unlock() because
202     // it overrides the member function
203     l32.unlock();
204     // regardless of serial or MT: will execute std::unique_lock::unlock()
205     // because std::unique_lock::unlock() is not virtual
206     as_unique_unlock(&l64);
207 
208     // NOTE ABOUT UNLOCKS:
209     // in MT, commenting out either
210     //      l32.unlock();
211     // or
212     //      as_unique_unlock(&l64);
213     // creates a deadlock; in serial, commenting out
214     //      as_unique_unlock(&l64);
215     // creates a deadlock but commenting out
216     //      l32.unlock();
217     // does not
218 
219     // clean up and join
220     for(uint64_t i = 0; i < n; ++i)
221     {
222         threads[i]->join();
223         delete threads[i];
224     }
225     threads.clear();
226 }
227 
228 //======================================================================================//
229 
230 int main()
231 {
232     print_threading();
233 
234     uint64_t n = 30;
235     std::cout << "\nRunning with real threads...\n" << std::endl;
236     exec<std::thread>(n);
237     std::cout << "\nRunning with fake threads...\n" << std::endl;
238     exec<DummyThread>(n);
239 
240 }
241 
242 ***/
243 
244 #pragma once
245 
246 #include "PTL/ConsumeParameters.hh"
247 #include "PTL/Types.hh"
248 
249 #include <chrono>
250 #include <iostream>
251 #include <mutex>
252 #include <system_error>
253 
254 namespace PTL
255 {
256 // Note: Note that TemplateAutoLock by itself is not thread-safe and
257 //       cannot be shared among threads due to the locked switch
258 //
259 template <typename MutexT>
260 class TemplateAutoLock : public std::unique_lock<MutexT>
261 {
262 public:
263     //------------------------------------------------------------------------//
264     // Some useful typedefs
265     //------------------------------------------------------------------------//
266     using unique_lock_t = std::unique_lock<MutexT>;
267     using this_type     = TemplateAutoLock<MutexT>;
268     using mutex_type    = typename unique_lock_t::mutex_type;
269 
270 public:
271     //------------------------------------------------------------------------//
272     // STL-consistent reference form constructors
273     //------------------------------------------------------------------------//
274 
275     // reference form is consistent with STL lock_guard types
276     // Locks the associated mutex by calling m.lock(). The behavior is
277     // undefined if the current thread already owns the mutex except when
278     // the mutex is recursive
279     explicit TemplateAutoLock(mutex_type& _mutex)
280     : unique_lock_t(_mutex, std::defer_lock)
281     {
282         // call termination-safe locking. if serial, this call has no effect
283         _lock_deferred();
284     }
285 
286     // Tries to lock the associated mutex by calling
287     // m.try_lock_for(_timeout_duration). Blocks until specified
288     // _timeout_duration has elapsed or the lock is acquired, whichever comes
289     // first. May block for longer than _timeout_duration.
290     template <typename Rep, typename Period>
291     TemplateAutoLock(mutex_type&                               _mutex,
292                      const std::chrono::duration<Rep, Period>& _timeout_duration)
293     : unique_lock_t(_mutex, std::defer_lock)
294     {
295         // call termination-safe locking. if serial, this call has no effect
296         _lock_deferred(_timeout_duration);
297     }
298 
299     // Tries to lock the associated mutex by calling
300     // m.try_lock_until(_timeout_time). Blocks until specified _timeout_time has
301     // been reached or the lock is acquired, whichever comes first. May block
302     // for longer than until _timeout_time has been reached.
303     template <typename Clock, typename Duration>
304     TemplateAutoLock(mutex_type&                                     _mutex,
305                      const std::chrono::time_point<Clock, Duration>& _timeout_time)
306     : unique_lock_t(_mutex, std::defer_lock)
307     {
308         // call termination-safe locking. if serial, this call has no effect
309         _lock_deferred(_timeout_time);
310     }
311 
312     // Does not lock the associated mutex.
313     TemplateAutoLock(mutex_type& _mutex, std::defer_lock_t _lock) noexcept
314     : unique_lock_t(_mutex, _lock)
315     {}
316 
317     // Tries to lock the associated mutex without blocking by calling
318     // m.try_lock(). The behavior is undefined if the current thread already
319     // owns the mutex except when the mutex is recursive.
320     TemplateAutoLock(mutex_type& _mutex, std::try_to_lock_t _lock)
321     : unique_lock_t(_mutex, _lock)
322     {}
323 
324     // Assumes the calling thread already owns m
325     TemplateAutoLock(mutex_type& _mutex, std::adopt_lock_t _lock)
326     : unique_lock_t(_mutex, _lock)
327     {}
328 
329 public:
330     //------------------------------------------------------------------------//
331     // Backwards compatibility versions (constructor with pointer to mutex)
332     //------------------------------------------------------------------------//
333     TemplateAutoLock(mutex_type* _mutex)
334     : unique_lock_t(*_mutex, std::defer_lock)
335     {
336         // call termination-safe locking. if serial, this call has no effect
337         _lock_deferred();
338     }
339 
340     TemplateAutoLock(mutex_type* _mutex, std::defer_lock_t _lock) noexcept
341     : unique_lock_t(*_mutex, _lock)
342     {}
343 
344     TemplateAutoLock(mutex_type* _mutex, std::try_to_lock_t _lock)
345     : unique_lock_t(*_mutex, _lock)
346     {}
347 
348     TemplateAutoLock(mutex_type* _mutex, std::adopt_lock_t _lock)
349     : unique_lock_t(*_mutex, _lock)
350     {}
351 
352 private:
353 // helpful macros
354 #define _is_stand_mutex(Tp) (std::is_same<Tp, Mutex>::value)
355 #define _is_recur_mutex(Tp) (std::is_same<Tp, RecursiveMutex>::value)
356 #define _is_other_mutex(Tp) (!_is_stand_mutex(Tp) && !_is_recur_mutex(Tp))
357 
358     template <typename Tp                                             = MutexT,
359               typename std::enable_if<_is_stand_mutex(Tp), int>::type = 0>
360     std::string GetTypeString()
361     {
362         return "AutoLock<Mutex>";
363     }
364 
365     template <typename Tp                                             = MutexT,
366               typename std::enable_if<_is_recur_mutex(Tp), int>::type = 0>
367     std::string GetTypeString()
368     {
369         return "AutoLock<RecursiveMutex>";
370     }
371 
372     template <typename Tp                                             = MutexT,
373               typename std::enable_if<_is_other_mutex(Tp), int>::type = 0>
374     std::string GetTypeString()
375     {
376         return "AutoLock<UNKNOWN_MUTEX>";
377     }
378 
379 // pollution is bad
380 #undef _is_stand_mutex
381 #undef _is_recur_mutex
382 #undef _is_other_mutex
383 
384     //========================================================================//
385     // NOTE on _lock_deferred(...) variants:
386     //      a system_error in lock means that the mutex is unavailable
387     //      we want to throw the error that comes from locking an unavailable
388     //      mutex so that we know there is a memory leak
389     //      if the mutex is valid, this will hold until the other thread
390     //      finishes
391 
392     // sometimes certain destructors use locks, this isn't an issue unless
393     // the object is leaked. When this occurs, the application finalization
394     // (i.e. the real or implied "return 0" part of main) will call destructors
395     // on Tasking object after some static mutex variables are deleted, leading
396     // to the error code (typically on Clang compilers):
397     //      libc++abi.dylib: terminating with uncaught exception of type
398     //      std::__1::system_error: mutex lock failed: Invalid argument
399     // this function protects against this failure until such a time that
400     // these issues have been resolved
401 
402     //========================================================================//
403     // standard locking
404     inline void _lock_deferred()
405     {
406         try
407         {
408             this->unique_lock_t::lock();
409         } catch(std::system_error& e)
410         {
411             PrintLockErrorMessage(e);
412         }
413     }
414 
415     //========================================================================//
416     // Tries to lock the associated mutex by calling
417     // m.try_lock_for(_timeout_duration). Blocks until specified
418     // _timeout_duration has elapsed or the lock is acquired, whichever comes
419     // first. May block for longer than _timeout_duration.
420     template <typename Rep, typename Period>
421     void _lock_deferred(const std::chrono::duration<Rep, Period>& _timeout_duration)
422     {
423         try
424         {
425             this->unique_lock_t::try_lock_for(_timeout_duration);
426         } catch(std::system_error& e)
427         {
428             PrintLockErrorMessage(e);
429         }
430     }
431 
432     //========================================================================//
433     // Tries to lock the associated mutex by calling
434     // m.try_lock_until(_timeout_time). Blocks until specified _timeout_time has
435     // been reached or the lock is acquired, whichever comes first. May block
436     // for longer than until _timeout_time has been reached.
437     template <typename Clock, typename Duration>
438     void _lock_deferred(const std::chrono::time_point<Clock, Duration>& _timeout_time)
439     {
440         try
441         {
442             this->unique_lock_t::try_lock_until(_timeout_time);
443         } catch(std::system_error& e)
444         {
445             PrintLockErrorMessage(e);
446         }
447     }
448 
449     //========================================================================//
450     // the message for what mutex lock fails due to deleted static mutex
451     // at termination
452     void PrintLockErrorMessage(std::system_error& e)
453     {
454         // use std::cout/std::endl to avoid include dependencies
455         using std::cout;
456         using std::endl;
457         // the error that comes from locking an unavailable mutex
458 #if defined(VERBOSE)
459         cout << "Non-critical error: mutex lock failure in "
460              << GetTypeString<mutex_type>() << ". "
461              << "If the app is terminating, Tasking failed to "
462              << "delete an allocated resource and a Tasking destructor is "
463              << "being called after the statics were destroyed. \n\t--> "
464              << "Exception: [code: " << e.code() << "] caught: " << e.what() << std::endl;
465 #else
466         ConsumeParameters(e);
467 #endif
468     }
469 };
470 
471 // -------------------------------------------------------------------------- //
472 //
473 //      Use the non-template types below:
474 //          - AutoLock with Mutex
475 //          - RecursiveAutoLock with RecursiveMutex
476 //
477 // -------------------------------------------------------------------------- //
478 
479 using AutoLock          = TemplateAutoLock<Mutex>;
480 using RecursiveAutoLock = TemplateAutoLock<RecursiveMutex>;
481 
482 }  // namespace PTL
483