ECCE @ EIC Software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
G4AutoLock.hh
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file G4AutoLock.hh
1 //
2 // ********************************************************************
3 // * License and Disclaimer *
4 // * *
5 // * The Geant4 software is copyright of the Copyright Holders of *
6 // * the Geant4 Collaboration. It is provided under the terms and *
7 // * conditions of the Geant4 Software License, included in the file *
8 // * LICENSE and available at http://cern.ch/geant4/license . These *
9 // * include a list of copyright holders. *
10 // * *
11 // * Neither the authors of this software system, nor their employing *
12 // * institutes,nor the agencies providing financial support for this *
13 // * work make any representation or warranty, express or implied, *
14 // * regarding this software system or assume any liability for its *
15 // * use. Please see the license in the file LICENSE and URL above *
16 // * for the full disclaimer and the limitation of liability. *
17 // * *
18 // * This code implementation is the result of the scientific and *
19 // * technical work of the GEANT4 collaboration. *
20 // * By using, copying, modifying or distributing the software (or *
21 // * any work based on the software) you agree to acknowledge its *
22 // * use in resulting scientific publications, and indicate your *
23 // * acceptance of all terms of the Geant4 Software license. *
24 // ********************************************************************
25 //
26 //
27 // ---------------------------------------------------------------
28 // GEANT 4 class header file
29 //
30 // Class Description:
31 //
32 // This class provides a mechanism to create a mutex and locks/unlocks it.
33 // Can be used by applications to implement in a portable way a mutexing logic.
34 // Usage Example:
35 //
36 // #include "G4Threading.hh"
37 // #include "G4AutoLock.hh"
38 //
39 // // defined somewhere -- static so all threads see the same mutex
40 // static G4Mutex aMutex;
41 //
42 // // somewhere else:
43 // // The G4AutoLock instance will automatically unlock the mutex when it
44 // // goes out of scope. One typically defines the scope within { } if
45 // // there is thread-safe code following the auto-lock
46 //
47 // {
48 // G4AutoLock l(&aMutex);
49 // ProtectedCode();
50 // }
51 //
52 // UnprotectedCode();
53 //
54 // // When ProtectedCode() is calling a function that also tries to lock
55 // // a normal G4AutoLock + G4Mutex will "deadlock". In other words, the
56 // // the mutex in the ProtectedCode() function will wait forever to
57 // // acquire the lock that is being held by the function that called
58 // // ProtectedCode(). In this situation, use a G4RecursiveAutoLock +
59 // // G4RecursiveMutex, e.g.
60 //
61 // // defined somewhere -- static so all threads see the same mutex
62 // static G4RecursiveMutex aRecursiveMutex;
63 //
64 // // this function is sometimes called directly and sometimes called
65 // // from SomeFunction_B(), which also locks the mutex
66 // void SomeFunction_A()
67 // {
68 // // when called from SomeFunction_B(), a G4Mutex + G4AutoLock will
69 // // deadlock
70 // G4RecursiveAutoLock l(&aRecursiveMutex);
71 // // do something
72 // }
73 //
74 // void SomeFunction_B()
75 // {
76 //
77 // {
78 // G4RecursiveAutoLock l(&aRecursiveMutex);
79 // SomeFunction_A();
80 // }
81 //
82 // UnprotectedCode();
83 // }
84 //
85 //
86 // ---------------------------------------------------------------
87 // Author: Andrea Dotti (15 Feb 2013): First Implementation
88 //
89 // Update: Jonathan Madsen (9 Feb 2018): Replaced custom implementation
90 // with inheritance from C++11 unique_lock, which inherits the
91 // following member functions:
92 //
93 // - unique_lock(unique_lock&& other) noexcept;
94 // - explicit unique_lock(mutex_type& m);
95 // - unique_lock(mutex_type& m, std::defer_lock_t t) noexcept;
96 // - unique_lock(mutex_type& m, std::try_to_lock_t t);
97 // - unique_lock(mutex_type& m, std::adopt_lock_t t);
98 //
99 // - template <typename Rep, typename Period>
100 // unique_lock(mutex_type& m,
101 // const std::chrono::duration<Rep,Period>& timeout_duration);
102 //
103 // - template<typename Clock, typename Duration>
104 // unique_lock(mutex_type& m,
105 // const std::chrono::time_point<Clock,Duration>& timeout_time);
106 //
107 // - void lock();
108 // - void unlock();
109 // - bool try_lock();
110 //
111 // - template <typename Rep, typename Period>
112 // bool try_lock_for(const std::chrono::duration<Rep,Period>&);
113 //
114 // - template <typename Rep, typename Period>
115 // bool try_lock_until(const std::chrono::time_point<Clock,Duration>&);
116 //
117 // - void swap(unique_lock& other) noexcept;
118 // - mutex_type* release() noexcept;
119 // - mutex_type* mutex() const noexcept;
120 // - bool owns_lock() const noexcept;
121 // - explicit operator bool() const noexcept;
122 // - unique_lock& operator=(unique_lock&& other);
123 //
124 // ---------------------------------------------------------------
125 //
126 // Note that G4AutoLock is defined also for a sequential Geant4 build but below
127 // regarding implementation (also found in G4Threading.hh)
128 //
129 //
130 // NOTE ON GEANT4 SERIAL BUILDS AND MUTEX/UNIQUE_LOCK
131 // ==================================================
132 //
133 // G4Mutex and G4RecursiveMutex are always C++11 std::mutex types
134 // however, in serial mode, using G4MUTEXLOCK and G4MUTEXUNLOCK on these
135 // types has no effect -- i.e. the mutexes are not actually locked or unlocked
136 //
137 // Additionally, when a G4Mutex or G4RecursiveMutex is used with G4AutoLock
138 // and G4RecursiveAutoLock, respectively, these classes also suppressing
139 // the locking and unlocking of the mutex. Regardless of the build type,
140 // G4AutoLock and G4RecursiveAutoLock inherit from std::unique_lock<std::mutex>
141 // and std::unique_lock<std::recursive_mutex>, respectively. This means
142 // that in situations (such as is needed by the analysis category), the
143 // G4AutoLock and G4RecursiveAutoLock can be passed to functions requesting
144 // a std::unique_lock. Within these functions, since std::unique_lock
145 // member functions are not virtual, they will not retain the dummy locking
146 // and unlocking behavior
147 // --> An example of this behavior can be found below
148 //
149 // Jonathan R. Madsen (February 21, 2018)
150 //
261 #ifndef G4AUTOLOCK_HH
262 #define G4AUTOLOCK_HH
263 
264 #include "G4Threading.hh"
265 
266 #include <mutex>
267 #include <chrono>
268 #include <system_error>
269 #include <iostream>
270 
271 // Note: Note that G4TemplateAutoLock by itself is not thread-safe and
272 // cannot be shared among threads due to the locked switch
273 //
274 template <typename _Mutex_t>
275 class G4TemplateAutoLock : public std::unique_lock<_Mutex_t>
276 {
277 public:
278  //------------------------------------------------------------------------//
279  // Some useful typedefs
280  //------------------------------------------------------------------------//
281  typedef std::unique_lock<_Mutex_t> unique_lock_t;
283  typedef typename unique_lock_t::mutex_type mutex_type;
284 
285 public:
286  //------------------------------------------------------------------------//
287  // STL-consistent reference form constructors
288  //------------------------------------------------------------------------//
289 
290  // reference form is consistent with STL lock_guard types
291  // Locks the associated mutex by calling m.lock(). The behavior is
292  // undefined if the current thread already owns the mutex except when
293  // the mutex is recursive
295  : unique_lock_t(_mutex, std::defer_lock)
296  {
297  // call termination-safe locking. if serial, this call has no effect
298  _lock_deferred();
299  }
300 
301  // Tries to lock the associated mutex by calling
302  // m.try_lock_for(_timeout_duration). Blocks until specified
303  // _timeout_duration has elapsed or the lock is acquired, whichever comes
304  // first. May block for longer than _timeout_duration.
305  template <typename Rep, typename Period>
307  const std::chrono::duration<Rep, Period>&
308  _timeout_duration)
309  : unique_lock_t(_mutex, std::defer_lock)
310  {
311  // call termination-safe locking. if serial, this call has no effect
312  _lock_deferred(_timeout_duration);
313  }
314 
315  // Tries to lock the associated mutex by calling
316  // m.try_lock_until(_timeout_time). Blocks until specified _timeout_time has
317  // been reached or the lock is acquired, whichever comes first. May block
318  // for longer than until _timeout_time has been reached.
319  template<typename Clock, typename Duration>
321  const std::chrono::time_point<Clock, Duration>&
322  _timeout_time)
323  : unique_lock_t(_mutex, std::defer_lock)
324  {
325  // call termination-safe locking. if serial, this call has no effect
326  _lock_deferred(_timeout_time);
327  }
328 
329  // Does not lock the associated mutex.
330  G4TemplateAutoLock(mutex_type& _mutex, std::defer_lock_t _lock) noexcept
331  : unique_lock_t(_mutex, _lock)
332  { }
333 
334 #ifdef G4MULTITHREADED
335 
336  // Tries to lock the associated mutex without blocking by calling
337  // m.try_lock(). The behavior is undefined if the current thread already
338  // owns the mutex except when the mutex is recursive.
339  G4TemplateAutoLock(mutex_type& _mutex, std::try_to_lock_t _lock)
340  : unique_lock_t(_mutex, _lock)
341  { }
342 
343  // Assumes the calling thread already owns m
344  G4TemplateAutoLock(mutex_type& _mutex, std::adopt_lock_t _lock)
345  : unique_lock_t(_mutex, _lock)
346  { }
347 
348 #else
349 
350  // serial dummy version (initializes unique_lock but does not lock)
351  G4TemplateAutoLock(mutex_type& _mutex, std::try_to_lock_t)
352  : unique_lock_t(_mutex, std::defer_lock)
353  { }
354 
355  // serial dummy version (initializes unique_lock but does not lock)
356  G4TemplateAutoLock(mutex_type& _mutex, std::adopt_lock_t)
357  : unique_lock_t(_mutex, std::defer_lock)
358  { }
359 
360 #endif // defined(G4MULTITHREADED)
361 
362 public:
363  //------------------------------------------------------------------------//
364  // Backwards compatibility versions (constructor with pointer to mutex)
365  //------------------------------------------------------------------------//
367  : unique_lock_t(*_mutex, std::defer_lock)
368  {
369  // call termination-safe locking. if serial, this call has no effect
370  _lock_deferred();
371  }
372 
373  G4TemplateAutoLock(mutex_type* _mutex, std::defer_lock_t _lock) noexcept
374  : unique_lock_t(*_mutex, _lock)
375  { }
376 
377 #if defined(G4MULTITHREADED)
378 
379  G4TemplateAutoLock(mutex_type* _mutex, std::try_to_lock_t _lock)
380  : unique_lock_t(*_mutex, _lock)
381  { }
382 
383  G4TemplateAutoLock(mutex_type* _mutex, std::adopt_lock_t _lock)
384  : unique_lock_t(*_mutex, _lock)
385  { }
386 
387 #else // NOT defined(G4MULTITHREADED) -- i.e. serial
388 
389  G4TemplateAutoLock(mutex_type* _mutex, std::try_to_lock_t)
390  : unique_lock_t(*_mutex, std::defer_lock)
391  { }
392 
393  G4TemplateAutoLock(mutex_type* _mutex, std::adopt_lock_t)
394  : unique_lock_t(*_mutex, std::defer_lock)
395  { }
396 
397 #endif // defined(G4MULTITHREADED)
398 
399 public:
400  //------------------------------------------------------------------------//
401  // Non-constructor overloads
402  //------------------------------------------------------------------------//
403 
404 #if defined(G4MULTITHREADED)
405 
406  // overload nothing
407 
408 #else // NOT defined(G4MULTITHREADED) -- i.e. serial
409 
410  // override unique lock member functions to keep from locking/unlocking
411  // but does not override in polymorphic usage
412  void lock() { }
413  void unlock() { }
414  bool try_lock() { return true; }
415 
416  template <typename Rep, typename Period>
417  bool try_lock_for(const std::chrono::duration<Rep, Period>&)
418  { return true; }
419 
420  template <typename Clock, typename Duration>
421  bool try_lock_until(const std::chrono::time_point<Clock, Duration>&)
422  { return true; }
423 
424  void swap(this_type& other) noexcept { std::swap(*this, other); }
425  bool owns_lock() const noexcept { return false; }
426 
427  // no need to overload
428  //explicit operator bool() const noexcept;
429  //this_type& operator=(this_type&& other);
430  //mutex_type* release() noexcept;
431  //mutex_type* mutex() const noexcept;
432 
433 #endif // defined(G4MULTITHREADED)
434 
435 private:
436  // helpful macros
437  #define _is_stand_mutex(_Tp) (std::is_same<_Tp, G4Mutex>::value)
438  #define _is_recur_mutex(_Tp) (std::is_same<_Tp, G4RecursiveMutex>::value)
439  #define _is_other_mutex(_Tp) (! _is_stand_mutex(_Tp) && ! _is_recur_mutex(_Tp) )
440 
441  template <typename _Tp = _Mutex_t,
442  typename std::enable_if<_is_stand_mutex(_Tp), int>::type = 0>
443  std::string GetTypeString() { return "G4AutoLock<G4Mutex>"; }
444 
445  template <typename _Tp = _Mutex_t,
446  typename std::enable_if<_is_recur_mutex(_Tp), int>::type = 0>
447  std::string GetTypeString() { return "G4AutoLock<G4RecursiveMutex>"; }
448 
449  template <typename _Tp = _Mutex_t,
450  typename std::enable_if<_is_other_mutex(_Tp), int>::type = 0>
451  std::string GetTypeString() { return "G4AutoLock<UNKNOWN_MUTEX>"; }
452 
453  // pollution is bad
454  #undef _is_stand_mutex
455  #undef _is_recur_mutex
456  #undef _is_other_mutex
457 
458  // used in _lock_deferred chrono variants to avoid ununsed-variable warning
459  template <typename _Tp>
460  void suppress_unused_variable(const _Tp&) { }
461 
462  //========================================================================//
463  // NOTE on _lock_deferred(...) variants:
464  // a system_error in lock means that the mutex is unavailable
465  // we want to throw the error that comes from locking an unavailable
466  // mutex so that we know there is a memory leak
467  // if the mutex is valid, this will hold until the other thread
468  // finishes
469 
470  // sometimes certain destructors use locks, this isn't an issue unless
471  // the object is leaked. When this occurs, the application finalization
472  // (i.e. the real or implied "return 0" part of main) will call destructors
473  // on Geant4 object after some static mutex variables are deleted, leading
474  // to the error code (typically on Clang compilers):
475  // libc++abi.dylib: terminating with uncaught exception of type
476  // std::__1::system_error: mutex lock failed: Invalid argument
477  // this function protects against this failure until such a time that
478  // these issues have been resolved
479 
480  //========================================================================//
481  // standard locking
482  inline void _lock_deferred()
483  {
484  #if defined(G4MULTITHREADED)
485  try { this->unique_lock_t::lock(); }
486  catch (std::system_error& e) { PrintLockErrorMessage(e); }
487  #endif
488  }
489 
490  //========================================================================//
491  // Tries to lock the associated mutex by calling
492  // m.try_lock_for(_timeout_duration). Blocks until specified
493  // _timeout_duration has elapsed or the lock is acquired, whichever comes
494  // first. May block for longer than _timeout_duration.
495  template <typename Rep, typename Period>
496  void _lock_deferred(const std::chrono::duration<Rep, Period>&
497  _timeout_duration)
498  {
499  #if defined(G4MULTITHREADED)
500  try { this->unique_lock_t::try_lock_for(_timeout_duration); }
501  catch (std::system_error& e) { PrintLockErrorMessage(e); }
502  #else
503  suppress_unused_variable(_timeout_duration);
504  #endif
505  }
506 
507  //========================================================================//
508  // Tries to lock the associated mutex by calling
509  // m.try_lock_until(_timeout_time). Blocks until specified _timeout_time has
510  // been reached or the lock is acquired, whichever comes first. May block
511  // for longer than until _timeout_time has been reached.
512  template<typename Clock, typename Duration>
513  void _lock_deferred(const std::chrono::time_point<Clock, Duration>&
514  _timeout_time)
515  {
516  #if defined(G4MULTITHREADED)
517  try { this->unique_lock_t::try_lock_until(_timeout_time); }
518  catch (std::system_error& e) { PrintLockErrorMessage(e); }
519  #else
520  suppress_unused_variable(_timeout_time);
521  #endif
522  }
523 
524  //========================================================================//
525  // the message for what mutex lock fails due to deleted static mutex
526  // at termination
527  void PrintLockErrorMessage(std::system_error& e)
528  {
529  // use std::cout/std::endl to avoid include dependencies
530  using std::cout;
531  using std::endl;
532  // the error that comes from locking an unavailable mutex
533  #if defined(G4VERBOSE)
534  cout << "Non-critical error: mutex lock failure in "
535  << GetTypeString<mutex_type>() << ". "
536  << "If the app is terminating, Geant4 failed to "
537  << "delete an allocated resource and a Geant4 destructor is "
538  << "being called after the statics were destroyed. \n\t--> "
539  << "Exception: [code: " << e.code() << "] caught: "
540  << e.what() << endl;
541  #else
543  #endif
544  }
545 
546 };
547 
548 // -------------------------------------------------------------------------- //
549 //
550 // Use the non-template types below:
551 // - G4AutoLock with G4Mutex
552 // - G4RecursiveAutoLock with G4RecursiveMutex
553 //
554 // -------------------------------------------------------------------------- //
555 
558 
559 // provide abbriviated type if another mutex type is desired to be used
560 // aside from above
561 template <typename _Tp> using G4TAutoLock = G4TemplateAutoLock<_Tp>;
562 
563 #endif //G4AUTOLOCK_HH