1 // 2 // Copyright 2021 The ANGLE Project Authors. All rights reserved. 3 // Use of this source code is governed by a BSD-style license that can be 4 // found in the LICENSE file. 5 // 6 // MultiThreadSteps.h: 7 // Synchronization help for tests that use multiple threads. 8 9 #include "gl_raii.h" 10 11 #include <atomic> 12 #include <condition_variable> 13 #include <functional> 14 #include <mutex> 15 #include <thread> 16 17 class EGLWindow; 18 19 namespace angle 20 { 21 namespace 22 { 23 // The following class is used by tests that need multiple threads that coordinate their actions 24 // via an enum of "steps". This enum is the template type E. The enum must have at least the 25 // following values: 26 // 27 // - Finish This value indicates that one thread has finished its last step and is cleaning up. 28 // The other thread waits for this before it does its last step and cleans up. 29 // - Abort This value indicates that one thread encountered a GL error and has exited. This 30 // will cause the other thread (that is waiting for a different step) to also abort. 31 // 32 // This class is RAII. It is declared at the top of a thread, and will be deconstructed at the end 33 // of the thread's outer block. If the thread encounters a GL error, the deconstructor will abort 34 // the other thread using the E:Abort step. 35 template <typename E> 36 class ThreadSynchronization 37 { 38 public: ThreadSynchronization(E * currentStep,std::mutex * mutex,std::condition_variable * condVar)39 ThreadSynchronization(E *currentStep, std::mutex *mutex, std::condition_variable *condVar) 40 : mCurrentStep(currentStep), mMutex(mutex), mCondVar(condVar) 41 {} ~ThreadSynchronization()42 ~ThreadSynchronization() 43 { 44 bool isAborting = false; 45 { 46 // If the other thread isn't finished, cause it to abort. 47 std::unique_lock<std::mutex> lock(*mMutex); 48 isAborting = *mCurrentStep != E::Finish; 49 50 if (isAborting) 51 { 52 *mCurrentStep = E::Abort; 53 } 54 } 55 mCondVar->notify_all(); 56 } 57 58 // Helper functions to synchronize the threads so that the operations are executed in the 59 // specific order the test is written for. waitForStep(E waitStep)60 bool waitForStep(E waitStep) 61 { 62 std::unique_lock<std::mutex> lock(*mMutex); 63 while (*mCurrentStep != waitStep) 64 { 65 // If necessary, abort execution as the other thread has encountered a GL error. 66 if (*mCurrentStep == E::Abort) 67 { 68 return false; 69 } 70 // Expect increasing order to reduce risk of race conditions / deadlocks. 71 if (*mCurrentStep > waitStep) 72 { 73 FATAL() << "waitForStep requires increasing order. mCurrentStep=" 74 << (int)*mCurrentStep << ", waitStep=" << (int)waitStep; 75 } 76 mCondVar->wait(lock); 77 } 78 79 return true; 80 } 81 nextStep(E newStep)82 void nextStep(E newStep) 83 { 84 { 85 std::unique_lock<std::mutex> lock(*mMutex); 86 *mCurrentStep = newStep; 87 } 88 mCondVar->notify_all(); 89 } 90 91 private: 92 E *mCurrentStep; 93 std::mutex *mMutex; 94 std::condition_variable *mCondVar; 95 }; 96 } // anonymous namespace 97 98 using LockStepThreadFunc = std::function<void(EGLDisplay, EGLSurface, EGLContext)>; 99 void RunLockStepThreads(EGLWindow *window, size_t threadCount, LockStepThreadFunc threadFuncs[]); 100 void RunLockStepThreadsWithSize(EGLWindow *window, 101 EGLint width, 102 EGLint height, 103 size_t threadCount, 104 LockStepThreadFunc threadFuncs[]); 105 } // namespace angle 106