Loading services/surfaceflinger/LocklessQueue.h 0 → 100644 +82 −0 Original line number Diff line number Diff line /* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include <atomic> #include <optional> template <typename T> // Single consumer multi producer stack. We can understand the two operations independently to see // why they are without race condition. // // push is responsible for maintaining a linked list stored in mPush, and called from multiple // threads without lock. We can see that if two threads never observe the same value from // mPush.load, it just functions as a normal linked list. In the case where two threads observe the // same value, one of them has to execute the compare_exchange first. The one that doesn't execute // the compare exchange first, will receive false from compare_exchange. previousHead is updated (by // compare_exchange) to the most recent value of mPush, and we try again. It's relatively clear to // see that the process can repeat with an arbitrary number of threads. // // Pop is much simpler. If mPop is empty (as it begins) it atomically exchanges // the entire push list with null. This is safe, since the only other reader (push) // of mPush will retry if it changes in between it's read and atomic compare. We // then store the list and pop one element. // // If we already had something in the pop list we just pop directly. class LocklessQueue { public: class Entry { public: T mValue; std::atomic<Entry*> mNext; Entry(T value) : mValue(value) {} }; std::atomic<Entry*> mPush = nullptr; std::atomic<Entry*> mPop = nullptr; bool isEmpty() { return (mPush.load() == nullptr) && (mPop.load() == nullptr); } void push(T value) { Entry* entry = new Entry(value); Entry* previousHead = mPush.load(/*std::memory_order_relaxed*/); do { entry->mNext = previousHead; } while (!mPush.compare_exchange_weak(previousHead, entry)); /*std::memory_order_release*/ } std::optional<T> pop() { Entry* popped = mPop.load(/*std::memory_order_acquire*/); if (popped) { // Single consumer so this is fine mPop.store(popped->mNext /* , std::memory_order_release */); auto value = popped->mValue; delete popped; return std::move(value); } else { Entry* grabbedList = mPush.exchange(nullptr /* , std::memory_order_acquire */); if (!grabbedList) return std::nullopt; // Reverse the list while (grabbedList->mNext) { Entry* next = grabbedList->mNext; grabbedList->mNext = popped; popped = grabbedList; grabbedList = next; } mPop.store(popped /* , std::memory_order_release */); auto value = grabbedList->mValue; delete grabbedList; return std::move(value); } } }; services/surfaceflinger/SurfaceFlinger.cpp +14 −13 Original line number Diff line number Diff line Loading @@ -3701,6 +3701,8 @@ int SurfaceFlinger::flushPendingTransactionQueues( transactions.emplace_back(std::move(transaction)); transactionQueue.pop(); mPendingTransactionCount--; ATRACE_INT("TransactionQueue", mPendingTransactionCount.load()); } if (transactionQueue.empty()) { Loading @@ -3724,8 +3726,6 @@ bool SurfaceFlinger::flushTransactionQueues(int64_t vsyncId) { { Mutex::Autolock _l(mStateLock); { Mutex::Autolock _l(mQueueLock); int lastTransactionsPendingBarrier = 0; int transactionsPendingBarrier = 0; // First collect transactions from the pending transaction queues. Loading @@ -3738,8 +3738,12 @@ bool SurfaceFlinger::flushTransactionQueues(int64_t vsyncId) { // Second, collect transactions from the transaction queue. // Here as well we are not allowing unsignaled buffers for the same // reason as above. while (!mTransactionQueue.empty()) { auto& transaction = mTransactionQueue.front(); while (!mLocklessTransactionQueue.isEmpty()) { auto maybeTransaction = mLocklessTransactionQueue.pop(); if (!maybeTransaction.has_value()) { break; } auto transaction = maybeTransaction.value(); const bool pendingTransactions = mPendingTransactionQueues.find(transaction.applyToken) != mPendingTransactionQueues.end(); Loading Loading @@ -3777,9 +3781,9 @@ bool SurfaceFlinger::flushTransactionQueues(int64_t vsyncId) { } }); transactions.emplace_back(std::move(transaction)); mPendingTransactionCount--; ATRACE_INT("TransactionQueue", mPendingTransactionCount.load()); } mTransactionQueue.pop_front(); ATRACE_INT("TransactionQueue", mTransactionQueue.size()); } // Transactions with a buffer pending on a barrier may be on a different applyToken Loading Loading @@ -3840,8 +3844,7 @@ bool SurfaceFlinger::applyTransactions(std::vector<TransactionState>& transactio } bool SurfaceFlinger::transactionFlushNeeded() { Mutex::Autolock _l(mQueueLock); return !mPendingTransactionQueues.empty() || !mTransactionQueue.empty(); return !mPendingTransactionQueues.empty() || !mLocklessTransactionQueue.isEmpty(); } bool SurfaceFlinger::frameIsEarly(nsecs_t expectedPresentTime, int64_t vsyncId) const { Loading Loading @@ -4008,17 +4011,15 @@ auto SurfaceFlinger::transactionIsReadyToBeApplied(TransactionState& transaction void SurfaceFlinger::queueTransaction(TransactionState& state) { state.queueTime = systemTime(); Mutex::Autolock lock(mQueueLock); // Generate a CountDownLatch pending state if this is a synchronous transaction. if (state.flags & eSynchronous) { state.transactionCommittedSignal = std::make_shared<CountDownLatch>(CountDownLatch::eSyncTransaction); } mTransactionQueue.emplace_back(state); ATRACE_INT("TransactionQueue", mTransactionQueue.size()); mLocklessTransactionQueue.push(state); mPendingTransactionCount++; ATRACE_INT("TransactionQueue", mPendingTransactionCount.load()); const auto schedule = [](uint32_t flags) { if (flags & eEarlyWakeupEnd) return TransactionSchedule::EarlyEnd; Loading services/surfaceflinger/SurfaceFlinger.h +7 −6 Original line number Diff line number Diff line Loading @@ -66,6 +66,7 @@ #include "FlagManager.h" #include "FrameTracker.h" #include "LayerVector.h" #include "LocklessQueue.h" #include "Scheduler/RefreshRateConfigs.h" #include "Scheduler/RefreshRateStats.h" #include "Scheduler/Scheduler.h" Loading Loading @@ -754,13 +755,13 @@ private: std::vector<TransactionState>& transactions, std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent, std::unordered_set<sp<IBinder>, SpHash<IBinder>>& applyTokensWithUnsignaledTransactions, bool tryApplyUnsignaled) REQUIRES(mStateLock, mQueueLock); bool tryApplyUnsignaled) REQUIRES(mStateLock); int flushUnsignaledPendingTransactionQueues( std::vector<TransactionState>& transactions, std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent, std::unordered_set<sp<IBinder>, SpHash<IBinder>>& applyTokensWithUnsignaledTransactions) REQUIRES(mStateLock, mQueueLock); REQUIRES(mStateLock); uint32_t setClientStateLocked(const FrameTimelineInfo&, ComposerState&, int64_t desiredPresentTime, bool isAutoTimestamp, Loading Loading @@ -1086,7 +1087,7 @@ private: status_t CheckTransactCodeCredentials(uint32_t code); // Add transaction to the Transaction Queue void queueTransaction(TransactionState& state) EXCLUDES(mQueueLock); void queueTransaction(TransactionState& state); void waitForSynchronousTransaction(const CountDownLatch& transactionCommittedSignal); void signalSynchronousTransactions(const uint32_t flag); Loading Loading @@ -1244,11 +1245,11 @@ private: uint32_t mTexturePoolSize = 0; std::vector<uint32_t> mTexturePool; mutable Mutex mQueueLock; Condition mTransactionQueueCV; std::unordered_map<sp<IBinder>, std::queue<TransactionState>, IListenerHash> mPendingTransactionQueues GUARDED_BY(mQueueLock); std::deque<TransactionState> mTransactionQueue GUARDED_BY(mQueueLock); mPendingTransactionQueues; LocklessQueue<TransactionState> mLocklessTransactionQueue; std::atomic<size_t> mPendingTransactionCount = 0; /* * Feature prototyping */ Loading services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +1 −1 Original line number Diff line number Diff line Loading @@ -724,7 +724,7 @@ public: return mFlinger->setPowerModeInternal(display, mode); } auto &getTransactionQueue() { return mFlinger->mTransactionQueue; } auto &getTransactionQueue() { return mFlinger->mLocklessTransactionQueue; } auto &getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; } auto setTransactionState( Loading services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +1 −1 Original line number Diff line number Diff line Loading @@ -421,7 +421,7 @@ public: return mFlinger->SurfaceFlinger::getDisplayNativePrimaries(displayToken, primaries); } auto& getTransactionQueue() { return mFlinger->mTransactionQueue; } auto& getTransactionQueue() { return mFlinger->mLocklessTransactionQueue; } auto& getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; } auto& getTransactionCommittedSignals() { return mFlinger->mTransactionCommittedSignals; } Loading Loading
services/surfaceflinger/LocklessQueue.h 0 → 100644 +82 −0 Original line number Diff line number Diff line /* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include <atomic> #include <optional> template <typename T> // Single consumer multi producer stack. We can understand the two operations independently to see // why they are without race condition. // // push is responsible for maintaining a linked list stored in mPush, and called from multiple // threads without lock. We can see that if two threads never observe the same value from // mPush.load, it just functions as a normal linked list. In the case where two threads observe the // same value, one of them has to execute the compare_exchange first. The one that doesn't execute // the compare exchange first, will receive false from compare_exchange. previousHead is updated (by // compare_exchange) to the most recent value of mPush, and we try again. It's relatively clear to // see that the process can repeat with an arbitrary number of threads. // // Pop is much simpler. If mPop is empty (as it begins) it atomically exchanges // the entire push list with null. This is safe, since the only other reader (push) // of mPush will retry if it changes in between it's read and atomic compare. We // then store the list and pop one element. // // If we already had something in the pop list we just pop directly. class LocklessQueue { public: class Entry { public: T mValue; std::atomic<Entry*> mNext; Entry(T value) : mValue(value) {} }; std::atomic<Entry*> mPush = nullptr; std::atomic<Entry*> mPop = nullptr; bool isEmpty() { return (mPush.load() == nullptr) && (mPop.load() == nullptr); } void push(T value) { Entry* entry = new Entry(value); Entry* previousHead = mPush.load(/*std::memory_order_relaxed*/); do { entry->mNext = previousHead; } while (!mPush.compare_exchange_weak(previousHead, entry)); /*std::memory_order_release*/ } std::optional<T> pop() { Entry* popped = mPop.load(/*std::memory_order_acquire*/); if (popped) { // Single consumer so this is fine mPop.store(popped->mNext /* , std::memory_order_release */); auto value = popped->mValue; delete popped; return std::move(value); } else { Entry* grabbedList = mPush.exchange(nullptr /* , std::memory_order_acquire */); if (!grabbedList) return std::nullopt; // Reverse the list while (grabbedList->mNext) { Entry* next = grabbedList->mNext; grabbedList->mNext = popped; popped = grabbedList; grabbedList = next; } mPop.store(popped /* , std::memory_order_release */); auto value = grabbedList->mValue; delete grabbedList; return std::move(value); } } };
services/surfaceflinger/SurfaceFlinger.cpp +14 −13 Original line number Diff line number Diff line Loading @@ -3701,6 +3701,8 @@ int SurfaceFlinger::flushPendingTransactionQueues( transactions.emplace_back(std::move(transaction)); transactionQueue.pop(); mPendingTransactionCount--; ATRACE_INT("TransactionQueue", mPendingTransactionCount.load()); } if (transactionQueue.empty()) { Loading @@ -3724,8 +3726,6 @@ bool SurfaceFlinger::flushTransactionQueues(int64_t vsyncId) { { Mutex::Autolock _l(mStateLock); { Mutex::Autolock _l(mQueueLock); int lastTransactionsPendingBarrier = 0; int transactionsPendingBarrier = 0; // First collect transactions from the pending transaction queues. Loading @@ -3738,8 +3738,12 @@ bool SurfaceFlinger::flushTransactionQueues(int64_t vsyncId) { // Second, collect transactions from the transaction queue. // Here as well we are not allowing unsignaled buffers for the same // reason as above. while (!mTransactionQueue.empty()) { auto& transaction = mTransactionQueue.front(); while (!mLocklessTransactionQueue.isEmpty()) { auto maybeTransaction = mLocklessTransactionQueue.pop(); if (!maybeTransaction.has_value()) { break; } auto transaction = maybeTransaction.value(); const bool pendingTransactions = mPendingTransactionQueues.find(transaction.applyToken) != mPendingTransactionQueues.end(); Loading Loading @@ -3777,9 +3781,9 @@ bool SurfaceFlinger::flushTransactionQueues(int64_t vsyncId) { } }); transactions.emplace_back(std::move(transaction)); mPendingTransactionCount--; ATRACE_INT("TransactionQueue", mPendingTransactionCount.load()); } mTransactionQueue.pop_front(); ATRACE_INT("TransactionQueue", mTransactionQueue.size()); } // Transactions with a buffer pending on a barrier may be on a different applyToken Loading Loading @@ -3840,8 +3844,7 @@ bool SurfaceFlinger::applyTransactions(std::vector<TransactionState>& transactio } bool SurfaceFlinger::transactionFlushNeeded() { Mutex::Autolock _l(mQueueLock); return !mPendingTransactionQueues.empty() || !mTransactionQueue.empty(); return !mPendingTransactionQueues.empty() || !mLocklessTransactionQueue.isEmpty(); } bool SurfaceFlinger::frameIsEarly(nsecs_t expectedPresentTime, int64_t vsyncId) const { Loading Loading @@ -4008,17 +4011,15 @@ auto SurfaceFlinger::transactionIsReadyToBeApplied(TransactionState& transaction void SurfaceFlinger::queueTransaction(TransactionState& state) { state.queueTime = systemTime(); Mutex::Autolock lock(mQueueLock); // Generate a CountDownLatch pending state if this is a synchronous transaction. if (state.flags & eSynchronous) { state.transactionCommittedSignal = std::make_shared<CountDownLatch>(CountDownLatch::eSyncTransaction); } mTransactionQueue.emplace_back(state); ATRACE_INT("TransactionQueue", mTransactionQueue.size()); mLocklessTransactionQueue.push(state); mPendingTransactionCount++; ATRACE_INT("TransactionQueue", mPendingTransactionCount.load()); const auto schedule = [](uint32_t flags) { if (flags & eEarlyWakeupEnd) return TransactionSchedule::EarlyEnd; Loading
services/surfaceflinger/SurfaceFlinger.h +7 −6 Original line number Diff line number Diff line Loading @@ -66,6 +66,7 @@ #include "FlagManager.h" #include "FrameTracker.h" #include "LayerVector.h" #include "LocklessQueue.h" #include "Scheduler/RefreshRateConfigs.h" #include "Scheduler/RefreshRateStats.h" #include "Scheduler/Scheduler.h" Loading Loading @@ -754,13 +755,13 @@ private: std::vector<TransactionState>& transactions, std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent, std::unordered_set<sp<IBinder>, SpHash<IBinder>>& applyTokensWithUnsignaledTransactions, bool tryApplyUnsignaled) REQUIRES(mStateLock, mQueueLock); bool tryApplyUnsignaled) REQUIRES(mStateLock); int flushUnsignaledPendingTransactionQueues( std::vector<TransactionState>& transactions, std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent, std::unordered_set<sp<IBinder>, SpHash<IBinder>>& applyTokensWithUnsignaledTransactions) REQUIRES(mStateLock, mQueueLock); REQUIRES(mStateLock); uint32_t setClientStateLocked(const FrameTimelineInfo&, ComposerState&, int64_t desiredPresentTime, bool isAutoTimestamp, Loading Loading @@ -1086,7 +1087,7 @@ private: status_t CheckTransactCodeCredentials(uint32_t code); // Add transaction to the Transaction Queue void queueTransaction(TransactionState& state) EXCLUDES(mQueueLock); void queueTransaction(TransactionState& state); void waitForSynchronousTransaction(const CountDownLatch& transactionCommittedSignal); void signalSynchronousTransactions(const uint32_t flag); Loading Loading @@ -1244,11 +1245,11 @@ private: uint32_t mTexturePoolSize = 0; std::vector<uint32_t> mTexturePool; mutable Mutex mQueueLock; Condition mTransactionQueueCV; std::unordered_map<sp<IBinder>, std::queue<TransactionState>, IListenerHash> mPendingTransactionQueues GUARDED_BY(mQueueLock); std::deque<TransactionState> mTransactionQueue GUARDED_BY(mQueueLock); mPendingTransactionQueues; LocklessQueue<TransactionState> mLocklessTransactionQueue; std::atomic<size_t> mPendingTransactionCount = 0; /* * Feature prototyping */ Loading
services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +1 −1 Original line number Diff line number Diff line Loading @@ -724,7 +724,7 @@ public: return mFlinger->setPowerModeInternal(display, mode); } auto &getTransactionQueue() { return mFlinger->mTransactionQueue; } auto &getTransactionQueue() { return mFlinger->mLocklessTransactionQueue; } auto &getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; } auto setTransactionState( Loading
services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +1 −1 Original line number Diff line number Diff line Loading @@ -421,7 +421,7 @@ public: return mFlinger->SurfaceFlinger::getDisplayNativePrimaries(displayToken, primaries); } auto& getTransactionQueue() { return mFlinger->mTransactionQueue; } auto& getTransactionQueue() { return mFlinger->mLocklessTransactionQueue; } auto& getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; } auto& getTransactionCommittedSignals() { return mFlinger->mTransactionCommittedSignals; } Loading