Back to home page

EIC code displayed by LXR

 
 

    


Warning, file /include/Geant4/PTL/AutoLock.hh was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 //
0002 // MIT License
0003 // Copyright (c) 2020 Jonathan R. Madsen
0004 // Permission is hereby granted, free of charge, to any person obtaining a copy
0005 // of this software and associated documentation files (the "Software"), to deal
0006 // in the Software without restriction, including without limitation the rights
0007 // to use, copy, modify, merge, publish, distribute, sublicense, and
0008 // copies of the Software, and to permit persons to whom the Software is
0009 // furnished to do so, subject to the following conditions:
0010 // The above copyright notice and this permission notice shall be included in
0011 // all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED
0012 // "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
0013 // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
0014 // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
0015 // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
0016 // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
0017 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
0018 //
0019 //
0020 // ---------------------------------------------------------------
0021 // Tasking class header file
0022 //
0023 /// Class Description:
0024 ///
0025 /// This class provides a mechanism to create a mutex and locks/unlocks it.
0026 /// Can be used by applications to implement in a portable way a mutexing logic.
0027 /// Usage Example:
0028 ///
0029 ///      #include "Threading.hh"
0030 ///      #include "AutoLock.hh"
0031 ///
0032 ///      /// defined somewhere -- static so all threads see the same mutex
0033 ///      static Mutex aMutex;
0034 ///
0035 ///      /// somewhere else:
0036 ///      /// The AutoLock instance will automatically unlock the mutex when it
0037 ///      /// goes out of scope. One typically defines the scope within { } if
0038 ///      /// there is thread-safe code following the auto-lock
0039 ///
0040 ///      {
0041 ///          AutoLock l(&aMutex);
0042 ///          ProtectedCode();
0043 ///      }
0044 ///
0045 ///      UnprotectedCode();
0046 ///
0047 ///      /// When ProtectedCode() is calling a function that also tries to lock
0048 ///      /// a normal AutoLock + Mutex will "deadlock". In other words, the
0049 ///      /// the mutex in the ProtectedCode() function will wait forever to
0050 ///      /// acquire the lock that is being held by the function that called
0051 ///      /// ProtectedCode(). In this situation, use a RecursiveAutoLock +
0052 ///      /// RecursiveMutex, e.g.
0053 ///
0054 ///      /// defined somewhere -- static so all threads see the same mutex
0055 ///      static RecursiveMutex aRecursiveMutex;
0056 ///
0057 ///      /// this function is sometimes called directly and sometimes called
0058 ///      /// from SomeFunction_B(), which also locks the mutex
0059 ///      void SomeFunction_A()
0060 ///      {
0061 ///          /// when called from SomeFunction_B(), a Mutex + AutoLock will
0062 ///          /// deadlock
0063 ///          RecursiveAutoLock l(&aRecursiveMutex);
0064 ///          /// do something
0065 ///      }
0066 ///
0067 ///      void SomeFunction_B()
0068 ///      {
0069 ///
0070 ///          {
0071 ///              RecursiveAutoLock l(&aRecursiveMutex);
0072 ///              SomeFunction_A();
0073 ///          }
0074 ///
0075 ///          UnprotectedCode();
0076 ///      }
0077 ///
0078 ///
0079 /// ---------------------------------------------------------------
0080 /// Author: Andrea Dotti (15 Feb 2013): First Implementation
0081 ///
0082 /// Update: Jonathan Madsen (9 Feb 2018): Replaced custom implementation
0083 ///      with inheritance from C++11 unique_lock, which inherits the
0084 ///      following member functions:
0085 ///
0086 ///      - unique_lock(unique_lock&& other) noexcept;
0087 ///      - explicit unique_lock(mutex_type& m);
0088 ///      - unique_lock(mutex_type& m, std::defer_lock_t t) noexcept;
0089 ///      - unique_lock(mutex_type& m, std::try_to_lock_t t);
0090 ///      - unique_lock(mutex_type& m, std::adopt_lock_t t);
0091 ///
0092 ///      - template <typename Rep, typename Period>
0093 ///        unique_lock(mutex_type& m,
0094 ///                   const std::chrono::duration<Rep,Period>&
0095 ///                   timeout_duration);
0096 ///
0097 ///      - template<typename Clock, typename Duration>
0098 ///        unique_lock(mutex_type& m,
0099 ///              const std::chrono::time_point<Clock,Duration>& timeout_time);
0100 ///
0101 ///      - void lock();
0102 ///      - void unlock();
0103 ///      - bool try_lock();
0104 ///
0105 ///      - template <typename Rep, typename Period>
0106 ///        bool try_lock_for(const std::chrono::duration<Rep,Period>&);
0107 ///
0108 ///      - template <typename Rep, typename Period>
0109 ///        bool try_lock_until(const std::chrono::time_point<Clock,Duration>&);
0110 ///
0111 ///      - void swap(unique_lock& other) noexcept;
0112 ///      - mutex_type* release() noexcept;
0113 ///      - mutex_type* mutex() const noexcept;
0114 ///      - bool owns_lock() const noexcept;
0115 ///      - explicit operator bool() const noexcept;
0116 ///      - unique_lock& operator=(unique_lock&& other);
0117 ///
0118 /// ---------------------------------------------------------------
0119 ///
0120 /// Note that AutoLock is defined also for a sequential Tasking build but below
0121 /// regarding implementation (also found in Threading.hh)
0122 ///
0123 ///
0124 ///          NOTE ON Tasking SERIAL BUILDS AND MUTEX/UNIQUE_LOCK
0125 ///          ==================================================
0126 ///
0127 /// Mutex and RecursiveMutex are always C++11 std::mutex types
0128 /// however, in serial mode, using MUTEXLOCK and MUTEXUNLOCK on these
0129 /// types has no effect -- i.e. the mutexes are not actually locked or unlocked
0130 ///
0131 /// Additionally, when a Mutex or RecursiveMutex is used with AutoLock
0132 /// and RecursiveAutoLock, respectively, these classes also suppressing
0133 /// the locking and unlocking of the mutex. Regardless of the build type,
0134 /// AutoLock and RecursiveAutoLock inherit from std::unique_lock<std::mutex>
0135 /// and std::unique_lock<std::recursive_mutex>, respectively. This means
0136 /// that in situations (such as is needed by the analysis category), the
0137 /// AutoLock and RecursiveAutoLock can be passed to functions requesting
0138 /// a std::unique_lock. Within these functions, since std::unique_lock
0139 /// member functions are not virtual, they will not retain the dummy locking
0140 /// and unlocking behavior
0141 /// --> An example of this behavior can be found below
0142 ///
0143 ///  Jonathan R. Madsen (February 21, 2018)
0144 ///
0145 /***
0146 
0147 //======================================================================================//
0148 
0149 typedef std::unique_lock<std::mutex> unique_lock_t;
0150 // functions for casting AutoLock to std::unique_lock to demonstrate
0151 // that AutoLock is NOT polymorphic
0152 void as_unique_lock(unique_lock_t* lock) { lock->lock(); }
0153 void as_unique_unlock(unique_lock_t* lock) { lock->unlock(); }
0154 
0155 //======================================================================================//
0156 
0157 void run(const uint64_t& n)
0158 {
0159     // sync the threads a bit
0160     std::this_thread::sleep_for(std::chrono::milliseconds(10));
0161 
0162     // get two mutexes to avoid deadlock when l32 actually locks
0163     AutoLock l32(TypeMutex<int32_t>(), std::defer_lock);
0164     AutoLock l64(TypeMutex<int64_t>(), std::defer_lock);
0165 
0166     // when serial: will not execute std::unique_lock::lock() because
0167     // it overrides the member function
0168     l32.lock();
0169     // regardless of serial or MT: will execute std::unique_lock::lock()
0170     // because std::unique_lock::lock() is not virtual
0171     as_unique_lock(&l64);
0172 
0173     std::cout << "Running iteration " << n << "..." << std::endl;
0174 }
0175 
0176 //======================================================================================//
0177 // execute some work
0178 template <typename thread_type = std::thread>
0179 void exec(uint64_t n)
0180 {
0181     // get two mutexes to avoid deadlock when l32 actually locks
0182     AutoLock l32(TypeMutex<int32_t>(), std::defer_lock);
0183     AutoLock l64(TypeMutex<int64_t>(), std::defer_lock);
0184 
0185     std::vector<thread_type*> threads(n, nullptr);
0186     for(uint64_t i = 0; i < n; ++i)
0187     {
0188         threads[i] = new thread_type();
0189         *(threads[i]) = std::move(thread_type(run, i));
0190     }
0191 
0192     // when serial: will not execute std::unique_lock::lock() because
0193     // it overrides the member function
0194     l32.lock();
0195     // regardless of serial or MT: will execute std::unique_lock::lock()
0196     // because std::unique_lock::lock() is not virtual
0197     as_unique_lock(&l64);
0198 
0199     std::cout << "Joining..." << std::endl;
0200 
0201     // when serial: will not execute std::unique_lock::unlock() because
0202     // it overrides the member function
0203     l32.unlock();
0204     // regardless of serial or MT: will execute std::unique_lock::unlock()
0205     // because std::unique_lock::unlock() is not virtual
0206     as_unique_unlock(&l64);
0207 
0208     // NOTE ABOUT UNLOCKS:
0209     // in MT, commenting out either
0210     //      l32.unlock();
0211     // or
0212     //      as_unique_unlock(&l64);
0213     // creates a deadlock; in serial, commenting out
0214     //      as_unique_unlock(&l64);
0215     // creates a deadlock but commenting out
0216     //      l32.unlock();
0217     // does not
0218 
0219     // clean up and join
0220     for(uint64_t i = 0; i < n; ++i)
0221     {
0222         threads[i]->join();
0223         delete threads[i];
0224     }
0225     threads.clear();
0226 }
0227 
0228 //======================================================================================//
0229 
0230 int main()
0231 {
0232     print_threading();
0233 
0234     uint64_t n = 30;
0235     std::cout << "\nRunning with real threads...\n" << std::endl;
0236     exec<std::thread>(n);
0237     std::cout << "\nRunning with fake threads...\n" << std::endl;
0238     exec<DummyThread>(n);
0239 
0240 }
0241 
0242 ***/
0243 
0244 #pragma once
0245 
0246 #include "PTL/ConsumeParameters.hh"
0247 #include "PTL/Types.hh"
0248 
0249 #include <chrono>
0250 #include <iostream>
0251 #include <mutex>
0252 #include <system_error>
0253 
0254 namespace PTL
0255 {
0256 // Note: Note that TemplateAutoLock by itself is not thread-safe and
0257 //       cannot be shared among threads due to the locked switch
0258 //
0259 template <typename MutexT>
0260 class TemplateAutoLock : public std::unique_lock<MutexT>
0261 {
0262 public:
0263     //------------------------------------------------------------------------//
0264     // Some useful typedefs
0265     //------------------------------------------------------------------------//
0266     using unique_lock_t = std::unique_lock<MutexT>;
0267     using this_type     = TemplateAutoLock<MutexT>;
0268     using mutex_type    = typename unique_lock_t::mutex_type;
0269 
0270 public:
0271     //------------------------------------------------------------------------//
0272     // STL-consistent reference form constructors
0273     //------------------------------------------------------------------------//
0274 
0275     // reference form is consistent with STL lock_guard types
0276     // Locks the associated mutex by calling m.lock(). The behavior is
0277     // undefined if the current thread already owns the mutex except when
0278     // the mutex is recursive
0279     explicit TemplateAutoLock(mutex_type& _mutex)
0280     : unique_lock_t(_mutex, std::defer_lock)
0281     {
0282         // call termination-safe locking. if serial, this call has no effect
0283         _lock_deferred();
0284     }
0285 
0286     // Tries to lock the associated mutex by calling
0287     // m.try_lock_for(_timeout_duration). Blocks until specified
0288     // _timeout_duration has elapsed or the lock is acquired, whichever comes
0289     // first. May block for longer than _timeout_duration.
0290     template <typename Rep, typename Period>
0291     TemplateAutoLock(mutex_type&                               _mutex,
0292                      const std::chrono::duration<Rep, Period>& _timeout_duration)
0293     : unique_lock_t(_mutex, std::defer_lock)
0294     {
0295         // call termination-safe locking. if serial, this call has no effect
0296         _lock_deferred(_timeout_duration);
0297     }
0298 
0299     // Tries to lock the associated mutex by calling
0300     // m.try_lock_until(_timeout_time). Blocks until specified _timeout_time has
0301     // been reached or the lock is acquired, whichever comes first. May block
0302     // for longer than until _timeout_time has been reached.
0303     template <typename Clock, typename Duration>
0304     TemplateAutoLock(mutex_type&                                     _mutex,
0305                      const std::chrono::time_point<Clock, Duration>& _timeout_time)
0306     : unique_lock_t(_mutex, std::defer_lock)
0307     {
0308         // call termination-safe locking. if serial, this call has no effect
0309         _lock_deferred(_timeout_time);
0310     }
0311 
0312     // Does not lock the associated mutex.
0313     TemplateAutoLock(mutex_type& _mutex, std::defer_lock_t _lock) noexcept
0314     : unique_lock_t(_mutex, _lock)
0315     {}
0316 
0317     // Tries to lock the associated mutex without blocking by calling
0318     // m.try_lock(). The behavior is undefined if the current thread already
0319     // owns the mutex except when the mutex is recursive.
0320     TemplateAutoLock(mutex_type& _mutex, std::try_to_lock_t _lock)
0321     : unique_lock_t(_mutex, _lock)
0322     {}
0323 
0324     // Assumes the calling thread already owns m
0325     TemplateAutoLock(mutex_type& _mutex, std::adopt_lock_t _lock)
0326     : unique_lock_t(_mutex, _lock)
0327     {}
0328 
0329 public:
0330     //------------------------------------------------------------------------//
0331     // Backwards compatibility versions (constructor with pointer to mutex)
0332     //------------------------------------------------------------------------//
0333     TemplateAutoLock(mutex_type* _mutex)
0334     : unique_lock_t(*_mutex, std::defer_lock)
0335     {
0336         // call termination-safe locking. if serial, this call has no effect
0337         _lock_deferred();
0338     }
0339 
0340     TemplateAutoLock(mutex_type* _mutex, std::defer_lock_t _lock) noexcept
0341     : unique_lock_t(*_mutex, _lock)
0342     {}
0343 
0344     TemplateAutoLock(mutex_type* _mutex, std::try_to_lock_t _lock)
0345     : unique_lock_t(*_mutex, _lock)
0346     {}
0347 
0348     TemplateAutoLock(mutex_type* _mutex, std::adopt_lock_t _lock)
0349     : unique_lock_t(*_mutex, _lock)
0350     {}
0351 
0352 private:
0353 // helpful macros
0354 #define _is_stand_mutex(Tp) (std::is_same<Tp, Mutex>::value)
0355 #define _is_recur_mutex(Tp) (std::is_same<Tp, RecursiveMutex>::value)
0356 #define _is_other_mutex(Tp) (!_is_stand_mutex(Tp) && !_is_recur_mutex(Tp))
0357 
0358     template <typename Tp                                             = MutexT,
0359               typename std::enable_if<_is_stand_mutex(Tp), int>::type = 0>
0360     std::string GetTypeString()
0361     {
0362         return "AutoLock<Mutex>";
0363     }
0364 
0365     template <typename Tp                                             = MutexT,
0366               typename std::enable_if<_is_recur_mutex(Tp), int>::type = 0>
0367     std::string GetTypeString()
0368     {
0369         return "AutoLock<RecursiveMutex>";
0370     }
0371 
0372     template <typename Tp                                             = MutexT,
0373               typename std::enable_if<_is_other_mutex(Tp), int>::type = 0>
0374     std::string GetTypeString()
0375     {
0376         return "AutoLock<UNKNOWN_MUTEX>";
0377     }
0378 
0379 // pollution is bad
0380 #undef _is_stand_mutex
0381 #undef _is_recur_mutex
0382 #undef _is_other_mutex
0383 
0384     //========================================================================//
0385     // NOTE on _lock_deferred(...) variants:
0386     //      a system_error in lock means that the mutex is unavailable
0387     //      we want to throw the error that comes from locking an unavailable
0388     //      mutex so that we know there is a memory leak
0389     //      if the mutex is valid, this will hold until the other thread
0390     //      finishes
0391 
0392     // sometimes certain destructors use locks, this isn't an issue unless
0393     // the object is leaked. When this occurs, the application finalization
0394     // (i.e. the real or implied "return 0" part of main) will call destructors
0395     // on Tasking object after some static mutex variables are deleted, leading
0396     // to the error code (typically on Clang compilers):
0397     //      libc++abi.dylib: terminating with uncaught exception of type
0398     //      std::__1::system_error: mutex lock failed: Invalid argument
0399     // this function protects against this failure until such a time that
0400     // these issues have been resolved
0401 
0402     //========================================================================//
0403     // standard locking
0404     inline void _lock_deferred()
0405     {
0406         try
0407         {
0408             this->unique_lock_t::lock();
0409         } catch(std::system_error& e)
0410         {
0411             PrintLockErrorMessage(e);
0412         }
0413     }
0414 
0415     //========================================================================//
0416     // Tries to lock the associated mutex by calling
0417     // m.try_lock_for(_timeout_duration). Blocks until specified
0418     // _timeout_duration has elapsed or the lock is acquired, whichever comes
0419     // first. May block for longer than _timeout_duration.
0420     template <typename Rep, typename Period>
0421     void _lock_deferred(const std::chrono::duration<Rep, Period>& _timeout_duration)
0422     {
0423         try
0424         {
0425             this->unique_lock_t::try_lock_for(_timeout_duration);
0426         } catch(std::system_error& e)
0427         {
0428             PrintLockErrorMessage(e);
0429         }
0430     }
0431 
0432     //========================================================================//
0433     // Tries to lock the associated mutex by calling
0434     // m.try_lock_until(_timeout_time). Blocks until specified _timeout_time has
0435     // been reached or the lock is acquired, whichever comes first. May block
0436     // for longer than until _timeout_time has been reached.
0437     template <typename Clock, typename Duration>
0438     void _lock_deferred(const std::chrono::time_point<Clock, Duration>& _timeout_time)
0439     {
0440         try
0441         {
0442             this->unique_lock_t::try_lock_until(_timeout_time);
0443         } catch(std::system_error& e)
0444         {
0445             PrintLockErrorMessage(e);
0446         }
0447     }
0448 
0449     //========================================================================//
0450     // the message for what mutex lock fails due to deleted static mutex
0451     // at termination
0452     void PrintLockErrorMessage(std::system_error& e)
0453     {
0454         // use std::cout/std::endl to avoid include dependencies
0455         using std::cout;
0456         using std::endl;
0457         // the error that comes from locking an unavailable mutex
0458 #if defined(VERBOSE)
0459         cout << "Non-critical error: mutex lock failure in "
0460              << GetTypeString<mutex_type>() << ". "
0461              << "If the app is terminating, Tasking failed to "
0462              << "delete an allocated resource and a Tasking destructor is "
0463              << "being called after the statics were destroyed. \n\t--> "
0464              << "Exception: [code: " << e.code() << "] caught: " << e.what() << std::endl;
0465 #else
0466         ConsumeParameters(e);
0467 #endif
0468     }
0469 };
0470 
0471 // -------------------------------------------------------------------------- //
0472 //
0473 //      Use the non-template types below:
0474 //          - AutoLock with Mutex
0475 //          - RecursiveAutoLock with RecursiveMutex
0476 //
0477 // -------------------------------------------------------------------------- //
0478 
0479 using AutoLock          = TemplateAutoLock<Mutex>;
0480 using RecursiveAutoLock = TemplateAutoLock<RecursiveMutex>;
0481 
0482 }  // namespace PTL