diff options
| author | 2022-10-05 16:59:56 -0700 | |
|---|---|---|
| committer | 2022-10-06 03:28:52 +0000 | |
| commit | 59f6d2df6cbc895651eb2628c9f98505ed9de8bf (patch) | |
| tree | 4ccb96004846f9fb507f0e413db68e4eafdffde0 | |
| parent | 1523dad8dc7c2cd9a110e0839ada541f49920a75 (diff) | |
SF: Add TransactionHandler
- migrate transaction queueing and flushing into
a new class and remove dependencies from
other components.
- Add a filter interface for other components to
participate in transactionready logic.
Test: presubmit
Bug: 238781169
Change-Id: Ia4da386cd72058126f6f765adafb9cb4d15b1d2b
| -rw-r--r-- | libs/gui/LayerState.cpp | 2 | ||||
| -rw-r--r-- | libs/gui/include/gui/fake/BufferData.h | 51 | ||||
| -rw-r--r-- | services/surfaceflinger/Android.bp | 1 | ||||
| -rw-r--r-- | services/surfaceflinger/Layer.h | 4 | ||||
| -rw-r--r-- | services/surfaceflinger/SurfaceFlinger.cpp | 332 | ||||
| -rw-r--r-- | services/surfaceflinger/SurfaceFlinger.h | 41 | ||||
| -rw-r--r-- | services/surfaceflinger/Tracing/TransactionProtoParser.h | 32 | ||||
| -rw-r--r-- | services/surfaceflinger/TransactionHandler.cpp | 188 | ||||
| -rw-r--r-- | services/surfaceflinger/TransactionHandler.h | 74 | ||||
| -rw-r--r-- | services/surfaceflinger/TransactionState.h | 9 | ||||
| -rw-r--r-- | services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h | 6 | ||||
| -rw-r--r-- | services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h | 9 | ||||
| -rw-r--r-- | services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp | 67 |
13 files changed, 490 insertions, 326 deletions
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 4d5978ccf7..2759c58fb1 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -634,7 +634,7 @@ bool layer_state_t::hasBufferChanges() const { } bool layer_state_t::hasValidBuffer() const { - return bufferData && (bufferData->buffer || bufferData->cachedBuffer.isValid()); + return bufferData && (bufferData->hasBuffer() || bufferData->cachedBuffer.isValid()); } status_t layer_state_t::matrix22_t::write(Parcel& output) const { diff --git a/libs/gui/include/gui/fake/BufferData.h b/libs/gui/include/gui/fake/BufferData.h new file mode 100644 index 0000000000..725d11c313 --- /dev/null +++ b/libs/gui/include/gui/fake/BufferData.h @@ -0,0 +1,51 @@ +/* + * 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 <gui/LayerState.h> + +namespace android::fake { + +// Class which exposes buffer properties from BufferData without holding on to an actual buffer +class BufferData : public android::BufferData { +public: + BufferData(uint64_t bufferId, uint32_t width, uint32_t height, int32_t pixelFormat, + uint64_t outUsage) + : mBufferId(bufferId), + mWidth(width), + mHeight(height), + mPixelFormat(pixelFormat), + mOutUsage(outUsage) {} + bool hasBuffer() const override { return mBufferId != 0; } + bool hasSameBuffer(const android::BufferData& other) const override { + return getId() == other.getId() && frameNumber == other.frameNumber; + } + uint32_t getWidth() const override { return mWidth; } + uint32_t getHeight() const override { return mHeight; } + uint64_t getId() const override { return mBufferId; } + PixelFormat getPixelFormat() const override { return mPixelFormat; } + uint64_t getUsage() const override { return mOutUsage; } + +private: + uint64_t mBufferId; + uint32_t mWidth; + uint32_t mHeight; + int32_t mPixelFormat; + uint64_t mOutUsage; +}; + +} // namespace android::fake diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index b911ae75d4..809c80be0d 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -194,6 +194,7 @@ filegroup { "Tracing/TransactionTracing.cpp", "Tracing/TransactionProtoParser.cpp", "TransactionCallbackInvoker.cpp", + "TransactionHandler.cpp", "TunnelModeEnabledReporter.cpp", ], } diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 4ff86e5dd6..418056c22c 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -881,7 +881,9 @@ public: bool mPendingHWCDestroy{false}; - bool backpressureEnabled() { return mDrawingState.flags & layer_state_t::eEnableBackpressure; } + bool backpressureEnabled() const { + return mDrawingState.flags & layer_state_t::eEnableBackpressure; + } bool setStretchEffect(const StretchEffect& effect); StretchEffect getStretchEffect() const; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 3419721865..002a637899 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -129,14 +129,11 @@ #include "LayerVector.h" #include "MutexUtils.h" #include "NativeWindowSurface.h" -#include "RefreshRateOverlay.h" #include "RegionSamplingThread.h" -#include "Scheduler/DispSyncSource.h" #include "Scheduler/EventThread.h" #include "Scheduler/LayerHistory.h" #include "Scheduler/Scheduler.h" #include "Scheduler/VsyncConfiguration.h" -#include "Scheduler/VsyncController.h" #include "StartPropertySetThread.h" #include "SurfaceFlingerProperties.h" #include "SurfaceInterceptor.h" @@ -775,6 +772,7 @@ chooseRenderEngineTypeViaSysProp() { void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { ALOGI( "SurfaceFlinger's main thread ready to run. " "Initializing graphics H/W..."); + addTransactionReadyFilters(); Mutex::Autolock lock(mStateLock); // Get a RenderEngine for the given display / config (can't fail) @@ -3691,122 +3689,117 @@ void SurfaceFlinger::setTransactionFlags(uint32_t mask, TransactionSchedule sche } } -int SurfaceFlinger::flushPendingTransactionQueues( - std::vector<TransactionState>& transactions, - std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent, - bool tryApplyUnsignaled) { - std::unordered_set<sp<IBinder>, SpHash<IBinder>> applyTokensWithUnsignaledTransactions; - int transactionsPendingBarrier = 0; - auto it = mPendingTransactionQueues.begin(); - while (it != mPendingTransactionQueues.end()) { - auto& [applyToken, transactionQueue] = *it; - while (!transactionQueue.empty()) { - // if we are in LatchUnsignaledConfig::AutoSingleLayer - // then we should have only one applyToken for processing. - // so we can stop further transactions on this applyToken. - if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer && - !applyTokensWithUnsignaledTransactions.empty()) { - ATRACE_NAME("stopTransactionProcessing"); - break; - } +TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyTimelineCheck( + const TransactionHandler::TransactionFlushState& flushState) { + using TransactionReadiness = TransactionHandler::TransactionReadiness; + const auto& transaction = *flushState.transaction; + ATRACE_FORMAT("transactionIsReadyToBeApplied vsyncId: %" PRId64, + transaction.frameTimelineInfo.vsyncId); + TimePoint desiredPresentTime = TimePoint::fromNs(transaction.desiredPresentTime); + // Do not present if the desiredPresentTime has not passed unless it is more than + // one second in the future. We ignore timestamps more than 1 second in the future + // for stability reasons. + if (!transaction.isAutoTimestamp && desiredPresentTime >= mExpectedPresentTime && + desiredPresentTime < mExpectedPresentTime + 1s) { + ATRACE_NAME("not current"); + return TransactionReadiness::NotReady; + } - auto& transaction = transactionQueue.front(); - const auto ready = - transactionIsReadyToBeApplied(transaction, transaction.frameTimelineInfo, - transaction.isAutoTimestamp, - TimePoint::fromNs(transaction.desiredPresentTime), - transaction.originUid, transaction.states, - bufferLayersReadyToPresent, transactions.size(), - tryApplyUnsignaled); - ATRACE_INT("TransactionReadiness", static_cast<int>(ready)); - if (ready == TransactionReadiness::NotReady) { - setTransactionFlags(eTransactionFlushNeeded); - break; - } - if (ready == TransactionReadiness::NotReadyBarrier) { - transactionsPendingBarrier++; - setTransactionFlags(eTransactionFlushNeeded); - break; + if (!mScheduler->isVsyncValid(mExpectedPresentTime, transaction.originUid)) { + ATRACE_NAME("!isVsyncValid"); + return TransactionReadiness::NotReady; + } + + // If the client didn't specify desiredPresentTime, use the vsyncId to determine the + // expected present time of this transaction. + if (transaction.isAutoTimestamp && + frameIsEarly(mExpectedPresentTime, VsyncId{transaction.frameTimelineInfo.vsyncId})) { + ATRACE_NAME("frameIsEarly"); + return TransactionReadiness::NotReady; + } + return TransactionReadiness::Ready; +} + +TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferCheck( + const TransactionHandler::TransactionFlushState& flushState) { + using TransactionReadiness = TransactionHandler::TransactionReadiness; + auto ready = TransactionReadiness::Ready; + flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const layer_state_t& s) -> bool { + sp<Layer> layer = Layer::fromHandle(s.surface).promote(); + const auto& transaction = *flushState.transaction; + // check for barrier frames + if (s.bufferData->hasBarrier && + ((layer->getDrawingState().frameNumber) < s.bufferData->barrierFrameNumber)) { + const bool willApplyBarrierFrame = + flushState.bufferLayersReadyToPresent.contains(s.surface.get()) && + (flushState.bufferLayersReadyToPresent.get(s.surface.get()) >= + s.bufferData->barrierFrameNumber); + if (!willApplyBarrierFrame) { + ATRACE_NAME("NotReadyBarrier"); + ready = TransactionReadiness::NotReadyBarrier; + return false; } - transaction.traverseStatesWithBuffers([&](const layer_state_t& state) { - const bool frameNumberChanged = state.bufferData->flags.test( - BufferData::BufferDataChange::frameNumberChanged); - if (frameNumberChanged) { - bufferLayersReadyToPresent[state.surface] = state.bufferData->frameNumber; - } else { - // Barrier function only used for BBQ which always includes a frame number - bufferLayersReadyToPresent[state.surface] = - std::numeric_limits<uint64_t>::max(); + } + + // If backpressure is enabled and we already have a buffer to commit, keep + // the transaction in the queue. + const bool hasPendingBuffer = + flushState.bufferLayersReadyToPresent.contains(s.surface.get()); + if (layer->backpressureEnabled() && hasPendingBuffer && transaction.isAutoTimestamp) { + ATRACE_NAME("hasPendingBuffer"); + ready = TransactionReadiness::NotReady; + return false; + } + + // check fence status + const bool allowLatchUnsignaled = shouldLatchUnsignaled(layer, s, transaction.states.size(), + flushState.firstTransaction); + ATRACE_FORMAT("%s allowLatchUnsignaled=%s", layer->getName().c_str(), + allowLatchUnsignaled ? "true" : "false"); + + const bool acquireFenceChanged = s.bufferData && + s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) && + s.bufferData->acquireFence; + const bool fenceSignaled = + (!acquireFenceChanged || + s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled); + if (!fenceSignaled) { + if (!allowLatchUnsignaled) { + ready = TransactionReadiness::NotReady; + auto& listener = s.bufferData->releaseBufferListener; + if (listener && + (flushState.queueProcessTime - transaction.postTime) > + std::chrono::nanoseconds(4s).count()) { + mTransactionHandler.onTransactionQueueStalled(transaction, listener); } - }); - const bool appliedUnsignaled = (ready == TransactionReadiness::ReadyUnsignaled); - if (appliedUnsignaled) { - applyTokensWithUnsignaledTransactions.insert(transaction.applyToken); + return false; } - transactions.emplace_back(std::move(transaction)); - transactionQueue.pop(); - mPendingTransactionCount--; - ATRACE_INT("TransactionQueue", mPendingTransactionCount.load()); + ready = enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer + ? TransactionReadiness::ReadyUnsignaledSingle + : TransactionReadiness::ReadyUnsignaled; } + return true; + }); + ATRACE_INT("TransactionReadiness", static_cast<int>(ready)); + return ready; +} - if (transactionQueue.empty()) { - it = mPendingTransactionQueues.erase(it); - } else { - it = std::next(it, 1); - } - } - return transactionsPendingBarrier; +void SurfaceFlinger::addTransactionReadyFilters() { + mTransactionHandler.addTransactionReadyFilter( + std::bind(&SurfaceFlinger::transactionReadyTimelineCheck, this, std::placeholders::_1)); + mTransactionHandler.addTransactionReadyFilter( + std::bind(&SurfaceFlinger::transactionReadyBufferCheck, this, std::placeholders::_1)); } bool SurfaceFlinger::flushTransactionQueues(VsyncId vsyncId) { // to prevent onHandleDestroyed from being called while the lock is held, // we must keep a copy of the transactions (specifically the composer // states) around outside the scope of the lock - std::vector<TransactionState> transactions; - // Layer handles that have transactions with buffers that are ready to be applied. - std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>> bufferLayersReadyToPresent; + std::vector<TransactionState> transactions = mTransactionHandler.flushTransactions(); { Mutex::Autolock _l(mStateLock); - { - while (!mLocklessTransactionQueue.isEmpty()) { - auto maybeTransaction = mLocklessTransactionQueue.pop(); - if (!maybeTransaction.has_value()) { - break; - } - auto transaction = maybeTransaction.value(); - mPendingTransactionQueues[transaction.applyToken].push(std::move(transaction)); - } - - // Transactions with a buffer pending on a barrier may be on a different applyToken - // than the transaction which satisfies our barrier. In fact this is the exact use case - // that the primitive is designed for. This means we may first process - // the barrier dependent transaction, determine it ineligible to complete - // and then satisfy in a later inner iteration of flushPendingTransactionQueues. - // The barrier dependent transaction was eligible to be presented in this frame - // but we would have prevented it without case. To fix this we continually - // loop through flushPendingTransactionQueues until we perform an iteration - // where the number of transactionsPendingBarrier doesn't change. This way - // we can continue to resolve dependency chains of barriers as far as possible. - int lastTransactionsPendingBarrier = 0; - int transactionsPendingBarrier = 0; - do { - lastTransactionsPendingBarrier = transactionsPendingBarrier; - transactionsPendingBarrier = - flushPendingTransactionQueues(transactions, bufferLayersReadyToPresent, - /*tryApplyUnsignaled*/ false); - } while (lastTransactionsPendingBarrier != transactionsPendingBarrier); - - // We collected all transactions that could apply without latching unsignaled buffers. - // If we are allowing latch unsignaled of some form, now it's the time to go over the - // transactions that were not applied and try to apply them unsignaled. - if (enableLatchUnsignaledConfig != LatchUnsignaledConfig::Disabled) { - flushPendingTransactionQueues(transactions, bufferLayersReadyToPresent, - /*tryApplyUnsignaled*/ true); - } - - return applyTransactions(transactions, vsyncId); - } + return applyTransactions(transactions, vsyncId); } } @@ -3833,7 +3826,7 @@ bool SurfaceFlinger::applyTransactions(std::vector<TransactionState>& transactio } bool SurfaceFlinger::transactionFlushNeeded() { - return !mPendingTransactionQueues.empty() || !mLocklessTransactionQueue.isEmpty(); + return mTransactionHandler.hasPendingTransactions(); } bool SurfaceFlinger::frameIsEarly(TimePoint expectedPresentTime, VsyncId vsyncId) const { @@ -3859,7 +3852,7 @@ bool SurfaceFlinger::frameIsEarly(TimePoint expectedPresentTime, VsyncId vsyncId } bool SurfaceFlinger::shouldLatchUnsignaled(const sp<Layer>& layer, const layer_state_t& state, - size_t numStates, size_t totalTXapplied) const { + size_t numStates, bool firstTransaction) const { if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::Disabled) { ALOGV("%s: false (LatchUnsignaledConfig::Disabled)", __func__); return false; @@ -3878,9 +3871,9 @@ bool SurfaceFlinger::shouldLatchUnsignaled(const sp<Layer>& layer, const layer_s } if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer) { - if (totalTXapplied > 0) { - ALOGV("%s: false (LatchUnsignaledConfig::AutoSingleLayer; totalTXapplied=%zu)", - __func__, totalTXapplied); + if (!firstTransaction) { + ALOGV("%s: false (LatchUnsignaledConfig::AutoSingleLayer; not first transaction)", + __func__); return false; } @@ -3904,116 +3897,6 @@ bool SurfaceFlinger::shouldLatchUnsignaled(const sp<Layer>& layer, const layer_s return true; } -auto SurfaceFlinger::transactionIsReadyToBeApplied( - TransactionState& transaction, const FrameTimelineInfo& info, bool isAutoTimestamp, - TimePoint desiredPresentTime, uid_t originUid, const Vector<ComposerState>& states, - const std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& - bufferLayersReadyToPresent, - size_t totalTXapplied, bool tryApplyUnsignaled) const -> TransactionReadiness { - ATRACE_FORMAT("transactionIsReadyToBeApplied vsyncId: %" PRId64, info.vsyncId); - // Do not present if the desiredPresentTime has not passed unless it is more than one second - // in the future. We ignore timestamps more than 1 second in the future for stability reasons. - if (!isAutoTimestamp && desiredPresentTime >= mExpectedPresentTime && - desiredPresentTime < mExpectedPresentTime + 1s) { - ATRACE_NAME("not current"); - return TransactionReadiness::NotReady; - } - - if (!mScheduler->isVsyncValid(mExpectedPresentTime, originUid)) { - ATRACE_NAME("!isVsyncValid"); - return TransactionReadiness::NotReady; - } - - // If the client didn't specify desiredPresentTime, use the vsyncId to determine the expected - // present time of this transaction. - if (isAutoTimestamp && frameIsEarly(mExpectedPresentTime, VsyncId{info.vsyncId})) { - ATRACE_NAME("frameIsEarly"); - return TransactionReadiness::NotReady; - } - - bool fenceUnsignaled = false; - auto queueProcessTime = systemTime(); - for (const ComposerState& state : states) { - const layer_state_t& s = state.state; - - sp<Layer> layer = nullptr; - if (s.surface) { - layer = fromHandle(s.surface).promote(); - } else if (s.hasBufferChanges()) { - ALOGW("Transaction with buffer, but no Layer?"); - continue; - } - if (!layer) { - continue; - } - - if (s.hasBufferChanges() && s.bufferData->hasBarrier && - ((layer->getDrawingState().frameNumber) < s.bufferData->barrierFrameNumber)) { - const bool willApplyBarrierFrame = - (bufferLayersReadyToPresent.find(s.surface) != bufferLayersReadyToPresent.end()) && - (bufferLayersReadyToPresent.at(s.surface) >= s.bufferData->barrierFrameNumber); - if (!willApplyBarrierFrame) { - ATRACE_NAME("NotReadyBarrier"); - return TransactionReadiness::NotReadyBarrier; - } - } - - const bool allowLatchUnsignaled = tryApplyUnsignaled && - shouldLatchUnsignaled(layer, s, states.size(), totalTXapplied); - ATRACE_FORMAT("%s allowLatchUnsignaled=%s", layer->getName().c_str(), - allowLatchUnsignaled ? "true" : "false"); - - const bool acquireFenceChanged = s.bufferData && - s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) && - s.bufferData->acquireFence; - fenceUnsignaled = fenceUnsignaled || - (acquireFenceChanged && - s.bufferData->acquireFence->getStatus() == Fence::Status::Unsignaled); - - if (fenceUnsignaled && !allowLatchUnsignaled) { - if (!transaction.sentFenceTimeoutWarning && - queueProcessTime - transaction.postTime > std::chrono::nanoseconds(4s).count()) { - transaction.sentFenceTimeoutWarning = true; - auto listener = s.bufferData->releaseBufferListener; - if (listener) { - listener->onTransactionQueueStalled(); - } - } - - ATRACE_NAME("fence unsignaled"); - return TransactionReadiness::NotReady; - } - - if (s.hasBufferChanges()) { - // If backpressure is enabled and we already have a buffer to commit, keep the - // transaction in the queue. - const bool hasPendingBuffer = bufferLayersReadyToPresent.find(s.surface) != - bufferLayersReadyToPresent.end(); - if (layer->backpressureEnabled() && hasPendingBuffer && isAutoTimestamp) { - ATRACE_NAME("hasPendingBuffer"); - return TransactionReadiness::NotReady; - } - } - } - return fenceUnsignaled ? TransactionReadiness::ReadyUnsignaled : TransactionReadiness::Ready; -} - -void SurfaceFlinger::queueTransaction(TransactionState& state) { - mLocklessTransactionQueue.push(state); - mPendingTransactionCount++; - ATRACE_INT("TransactionQueue", mPendingTransactionCount.load()); - - const auto schedule = [](uint32_t flags) { - if (flags & eEarlyWakeupEnd) return TransactionSchedule::EarlyEnd; - if (flags & eEarlyWakeupStart) return TransactionSchedule::EarlyStart; - return TransactionSchedule::Late; - }(state.flags); - - const auto frameHint = state.isFrameActive() ? FrameHint::kActive : FrameHint::kNone; - - setTransactionFlags(eTransactionFlushNeeded, schedule, state.applyToken, frameHint); -} - status_t SurfaceFlinger::setTransactionState( const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& states, const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken, @@ -4064,7 +3947,16 @@ status_t SurfaceFlinger::setTransactionState( if (mTransactionTracing) { mTransactionTracing->addQueuedTransaction(state); } - queueTransaction(state); + + const auto schedule = [](uint32_t flags) { + if (flags & eEarlyWakeupEnd) return TransactionSchedule::EarlyEnd; + if (flags & eEarlyWakeupStart) return TransactionSchedule::EarlyStart; + return TransactionSchedule::Late; + }(state.flags); + + const auto frameHint = state.isFrameActive() ? FrameHint::kActive : FrameHint::kNone; + setTransactionFlags(eTransactionFlushNeeded, schedule, state.applyToken, frameHint); + mTransactionHandler.queueTransaction(std::move(state)); return NO_ERROR; } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index baaf41ec02..f4fb8de010 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -57,7 +57,6 @@ #include <scheduler/Time.h> #include <ui/FenceResult.h> -#include "ClientCache.h" #include "Display/DisplayMap.h" #include "Display/PhysicalDisplay.h" #include "DisplayDevice.h" @@ -66,19 +65,17 @@ #include "DisplayIdGenerator.h" #include "Effects/Daltonizer.h" #include "FlagManager.h" -#include "FrameTracker.h" #include "LayerVector.h" -#include "LocklessQueue.h" #include "Scheduler/RefreshRateConfigs.h" #include "Scheduler/RefreshRateStats.h" #include "Scheduler/Scheduler.h" #include "Scheduler/VsyncModulator.h" #include "SurfaceFlingerFactory.h" #include "ThreadContext.h" -#include "TracedOrdinal.h" #include "Tracing/LayerTracing.h" #include "Tracing/TransactionTracing.h" #include "TransactionCallbackInvoker.h" +#include "TransactionHandler.h" #include "TransactionState.h" #include <atomic> @@ -341,6 +338,7 @@ private: friend class RegionSamplingThread; friend class LayerRenderArea; friend class LayerTracing; + friend class SurfaceComposerAIDL; // For unit tests friend class TestableSurfaceFlinger; @@ -729,11 +727,13 @@ private: bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext); // Returns true if there is at least one transaction that needs to be flushed bool transactionFlushNeeded(); - - int flushPendingTransactionQueues( - std::vector<TransactionState>& transactions, - std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent, - bool tryApplyUnsignaled) REQUIRES(mStateLock) REQUIRES(kMainThreadContext); + void addTransactionReadyFilters(); + TransactionHandler::TransactionReadiness transactionReadyTimelineCheck( + const TransactionHandler::TransactionFlushState& flushState) + REQUIRES(kMainThreadContext); + TransactionHandler::TransactionReadiness transactionReadyBufferCheck( + const TransactionHandler::TransactionFlushState& flushState) + REQUIRES(kMainThreadContext); uint32_t setClientStateLocked(const FrameTimelineInfo&, ComposerState&, int64_t desiredPresentTime, bool isAutoTimestamp, @@ -751,23 +751,9 @@ private: void commitOffscreenLayers(); - enum class TransactionReadiness { - NotReady, - NotReadyBarrier, - Ready, - ReadyUnsignaled, - }; - TransactionReadiness transactionIsReadyToBeApplied( - TransactionState&, const FrameTimelineInfo&, bool isAutoTimestamp, - TimePoint desiredPresentTime, uid_t originUid, const Vector<ComposerState>&, - const std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& - bufferLayersReadyToPresent, - size_t totalTXapplied, bool tryApplyUnsignaled) const REQUIRES(mStateLock) - REQUIRES(kMainThreadContext); - static LatchUnsignaledConfig getLatchUnsignaledConfig(); bool shouldLatchUnsignaled(const sp<Layer>& layer, const layer_state_t&, size_t numStates, - size_t totalTXapplied) const; + bool firstTransaction) const; bool applyTransactions(std::vector<TransactionState>& transactions, VsyncId) REQUIRES(mStateLock); uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock); @@ -1094,7 +1080,6 @@ private: status_t CheckTransactCodeCredentials(uint32_t code); // Add transaction to the Transaction Queue - void queueTransaction(TransactionState& state); /* * Generic Layer Metadata @@ -1256,10 +1241,6 @@ private: uint32_t mTexturePoolSize = 0; std::vector<uint32_t> mTexturePool; - std::unordered_map<sp<IBinder>, std::queue<TransactionState>, IListenerHash> - mPendingTransactionQueues; - LocklessQueue<TransactionState> mLocklessTransactionQueue; - std::atomic<size_t> mPendingTransactionCount = 0; std::atomic<size_t> mNumLayers = 0; // to linkToDeath @@ -1415,7 +1396,7 @@ private: bool early = false; } mPowerHintSessionMode; - friend class SurfaceComposerAIDL; + TransactionHandler mTransactionHandler; }; class SurfaceComposerAIDL : public gui::BnSurfaceComposer { diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.h b/services/surfaceflinger/Tracing/TransactionProtoParser.h index 872a901b21..2232bb9cb1 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.h +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.h @@ -15,6 +15,7 @@ */ #pragma once +#include <gui/fake/BufferData.h> #include <layerproto/TransactionProto.h> #include <utils/RefBase.h> @@ -43,35 +44,6 @@ struct TracingLayerState : layer_state_t { TracingLayerCreationArgs args; }; -// Class which exposes buffer properties from BufferData without holding on to the actual buffer -// handle. -class BufferDataStub : public BufferData { -public: - BufferDataStub(uint64_t bufferId, uint32_t width, uint32_t height, int32_t pixelFormat, - uint64_t outUsage) - : mBufferId(bufferId), - mWidth(width), - mHeight(height), - mPixelFormat(pixelFormat), - mOutUsage(outUsage) {} - bool hasBuffer() const override { return mBufferId != 0; } - bool hasSameBuffer(const BufferData& other) const override { - return getId() == other.getId() && frameNumber == other.frameNumber; - } - uint32_t getWidth() const override { return mWidth; } - uint32_t getHeight() const override { return mHeight; } - uint64_t getId() const override { return mBufferId; } - PixelFormat getPixelFormat() const override { return mPixelFormat; } - uint64_t getUsage() const override { return mOutUsage; } - -private: - uint64_t mBufferId; - uint32_t mWidth; - uint32_t mHeight; - int32_t mPixelFormat; - uint64_t mOutUsage; -}; - class TransactionProtoParser { public: // Utility class to map handles to ids and buffers to buffer properties without pulling @@ -87,7 +59,7 @@ public: virtual std::shared_ptr<BufferData> getGraphicData(uint64_t bufferId, uint32_t width, uint32_t height, int32_t pixelFormat, uint64_t usage) const { - return std::make_shared<BufferDataStub>(bufferId, width, height, pixelFormat, usage); + return std::make_shared<fake::BufferData>(bufferId, width, height, pixelFormat, usage); } virtual void getGraphicBufferPropertiesFromCache(client_cache_t /* cachedBuffer */, uint64_t* /* outBufferId */, diff --git a/services/surfaceflinger/TransactionHandler.cpp b/services/surfaceflinger/TransactionHandler.cpp new file mode 100644 index 0000000000..6c6a487b67 --- /dev/null +++ b/services/surfaceflinger/TransactionHandler.cpp @@ -0,0 +1,188 @@ +/* + * 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. + */ + +// #define LOG_NDEBUG 0 +#undef LOG_TAG +#define LOG_TAG "TransactionHandler" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +#include <cutils/trace.h> +#include <utils/Log.h> + +#include "TransactionHandler.h" + +namespace android { + +void TransactionHandler::queueTransaction(TransactionState&& state) { + mLocklessTransactionQueue.push(std::move(state)); + mPendingTransactionCount.fetch_add(1); + ATRACE_INT("TransactionQueue", static_cast<int>(mPendingTransactionCount.load())); +} + +std::vector<TransactionState> TransactionHandler::flushTransactions() { + while (!mLocklessTransactionQueue.isEmpty()) { + auto maybeTransaction = mLocklessTransactionQueue.pop(); + if (!maybeTransaction.has_value()) { + break; + } + auto transaction = maybeTransaction.value(); + mPendingTransactionQueues[transaction.applyToken].emplace(std::move(transaction)); + } + + // Collect transaction that are ready to be applied. + std::vector<TransactionState> transactions; + TransactionFlushState flushState; + flushState.queueProcessTime = systemTime(); + // Transactions with a buffer pending on a barrier may be on a different applyToken + // than the transaction which satisfies our barrier. In fact this is the exact use case + // that the primitive is designed for. This means we may first process + // the barrier dependent transaction, determine it ineligible to complete + // and then satisfy in a later inner iteration of flushPendingTransactionQueues. + // The barrier dependent transaction was eligible to be presented in this frame + // but we would have prevented it without case. To fix this we continually + // loop through flushPendingTransactionQueues until we perform an iteration + // where the number of transactionsPendingBarrier doesn't change. This way + // we can continue to resolve dependency chains of barriers as far as possible. + int lastTransactionsPendingBarrier = 0; + int transactionsPendingBarrier = 0; + do { + lastTransactionsPendingBarrier = transactionsPendingBarrier; + // Collect transactions that are ready to be applied. + transactionsPendingBarrier = flushPendingTransactionQueues(transactions, flushState); + } while (lastTransactionsPendingBarrier != transactionsPendingBarrier); + + mPendingTransactionCount.fetch_sub(transactions.size()); + ATRACE_INT("TransactionQueue", static_cast<int>(mPendingTransactionCount.load())); + return transactions; +} + +TransactionHandler::TransactionReadiness TransactionHandler::applyFilters( + TransactionFlushState& flushState) { + auto ready = TransactionReadiness::Ready; + for (auto& filter : mTransactionReadyFilters) { + auto perFilterReady = filter(flushState); + switch (perFilterReady) { + case TransactionReadiness::NotReady: + case TransactionReadiness::NotReadyBarrier: + return perFilterReady; + + case TransactionReadiness::ReadyUnsignaled: + case TransactionReadiness::ReadyUnsignaledSingle: + // If one of the filters allows latching an unsignaled buffer, latch this ready + // state. + ready = perFilterReady; + break; + case TransactionReadiness::Ready: + continue; + } + } + return ready; +} + +int TransactionHandler::flushPendingTransactionQueues(std::vector<TransactionState>& transactions, + TransactionFlushState& flushState) { + int transactionsPendingBarrier = 0; + auto it = mPendingTransactionQueues.begin(); + while (it != mPendingTransactionQueues.end()) { + auto& queue = it->second; + IBinder* queueToken = it->first.get(); + + // if we have already flushed a transaction with an unsignaled buffer then stop queue + // processing + if (std::find(flushState.queuesWithUnsignaledBuffers.begin(), + flushState.queuesWithUnsignaledBuffers.end(), + queueToken) != flushState.queuesWithUnsignaledBuffers.end()) { + continue; + } + + while (!queue.empty()) { + auto& transaction = queue.front(); + flushState.transaction = &transaction; + auto ready = applyFilters(flushState); + if (ready == TransactionReadiness::NotReadyBarrier) { + transactionsPendingBarrier++; + break; + } else if (ready == TransactionReadiness::NotReady) { + break; + } + + // Transaction is ready move it from the pending queue. + flushState.firstTransaction = false; + removeFromStalledTransactions(transaction.id); + transactions.emplace_back(std::move(transaction)); + queue.pop(); + + // If the buffer is unsignaled, then we don't want to signal other transactions using + // the buffer as a barrier. + auto& readyToApplyTransaction = transactions.back(); + if (ready == TransactionReadiness::Ready) { + readyToApplyTransaction.traverseStatesWithBuffers([&](const layer_state_t& state) { + const bool frameNumberChanged = state.bufferData->flags.test( + BufferData::BufferDataChange::frameNumberChanged); + if (frameNumberChanged) { + flushState.bufferLayersReadyToPresent + .emplace_or_replace(state.surface.get(), + state.bufferData->frameNumber); + } else { + // Barrier function only used for BBQ which always includes a frame number. + // This value only used for barrier logic. + flushState.bufferLayersReadyToPresent + .emplace_or_replace(state.surface.get(), + std::numeric_limits<uint64_t>::max()); + } + }); + } else if (ready == TransactionReadiness::ReadyUnsignaledSingle) { + // Track queues with a flushed unsingaled buffer. + flushState.queuesWithUnsignaledBuffers.emplace_back(queueToken); + break; + } + } + + if (queue.empty()) { + it = mPendingTransactionQueues.erase(it); + } else { + it = std::next(it, 1); + } + } + return transactionsPendingBarrier; +} + +void TransactionHandler::addTransactionReadyFilter(TransactionFilter&& filter) { + mTransactionReadyFilters.emplace_back(std::move(filter)); +} + +bool TransactionHandler::hasPendingTransactions() { + return !mPendingTransactionQueues.empty() || !mLocklessTransactionQueue.isEmpty(); +} + +void TransactionHandler::onTransactionQueueStalled(const TransactionState& transaction, + sp<ITransactionCompletedListener>& listener) { + if (std::find(mStalledTransactions.begin(), mStalledTransactions.end(), transaction.id) != + mStalledTransactions.end()) { + return; + } + + mStalledTransactions.push_back(transaction.id); + listener->onTransactionQueueStalled(); +} + +void TransactionHandler::removeFromStalledTransactions(uint64_t id) { + auto it = std::find(mStalledTransactions.begin(), mStalledTransactions.end(), id); + if (it != mStalledTransactions.end()) { + mStalledTransactions.erase(it); + } +} +} // namespace android
\ No newline at end of file diff --git a/services/surfaceflinger/TransactionHandler.h b/services/surfaceflinger/TransactionHandler.h new file mode 100644 index 0000000000..237b48d55f --- /dev/null +++ b/services/surfaceflinger/TransactionHandler.h @@ -0,0 +1,74 @@ +/* + * 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 <semaphore.h> +#include <cstdint> +#include <mutex> +#include <queue> +#include <thread> +#include <vector> + +#include <LocklessQueue.h> +#include <TransactionState.h> +#include <android-base/thread_annotations.h> +#include <ftl/small_map.h> +#include <ftl/small_vector.h> + +namespace android { +class TransactionHandler { +public: + struct TransactionFlushState { + const TransactionState* transaction; + bool firstTransaction = true; + nsecs_t queueProcessTime = 0; + // Layer handles that have transactions with buffers that are ready to be applied. + ftl::SmallMap<IBinder* /* binder address */, uint64_t /* framenumber */, 15> + bufferLayersReadyToPresent = {}; + ftl::SmallVector<IBinder* /* queueToken */, 15> queuesWithUnsignaledBuffers; + }; + enum class TransactionReadiness { + NotReady, + NotReadyBarrier, + Ready, + ReadyUnsignaled, + ReadyUnsignaledSingle, + }; + using TransactionFilter = std::function<TransactionReadiness(const TransactionFlushState&)>; + + bool hasPendingTransactions(); + std::vector<TransactionState> flushTransactions(); + void addTransactionReadyFilter(TransactionFilter&&); + void queueTransaction(TransactionState&&); + void onTransactionQueueStalled(const TransactionState&, sp<ITransactionCompletedListener>&); + void removeFromStalledTransactions(uint64_t transactionId); + +private: + // For unit tests + friend class TestableSurfaceFlinger; + + int flushPendingTransactionQueues(std::vector<TransactionState>&, TransactionFlushState&); + TransactionReadiness applyFilters(TransactionFlushState&); + std::unordered_map<sp<IBinder>, std::queue<TransactionState>, IListenerHash> + mPendingTransactionQueues; + LocklessQueue<TransactionState> mLocklessTransactionQueue; + std::atomic<size_t> mPendingTransactionCount = 0; + ftl::SmallVector<TransactionFilter, 2> mTransactionReadyFilters; + std::vector<uint64_t> mStalledTransactions; +}; + +} // namespace android diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index 95eb503327..3cbfe811ea 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -64,6 +64,15 @@ struct TransactionState { } } + template <typename Visitor> + void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) const { + for (const auto& [state] : states) { + if (state.hasBufferChanges() && state.hasValidBuffer() && state.surface) { + if (!visitor(state)) return; + } + } + } + // TODO(b/185535769): Remove FrameHint. Instead, reset the idle timer (of the relevant physical // display) on the main thread if commit leads to composite. Then, RefreshRateOverlay should be // able to setFrameRate once, rather than for each transaction. diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 370b23c7ce..49dd80ede7 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -728,8 +728,10 @@ public: return mFlinger->setPowerModeInternal(display, mode); } - auto &getTransactionQueue() { return mFlinger->mLocklessTransactionQueue; } - auto &getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; } + auto &getTransactionQueue() { return mFlinger->mTransactionHandler.mLocklessTransactionQueue; } + auto &getPendingTransactionQueue() { + return mFlinger->mTransactionHandler.mPendingTransactionQueues; + } auto setTransactionState( const FrameTimelineInfo &frameTimelineInfo, const Vector<ComposerState> &states, diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index e1a0eeb7fe..7b319f52c5 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -417,8 +417,13 @@ public: return mFlinger->SurfaceFlinger::getDisplayNativePrimaries(displayToken, primaries); } - auto& getTransactionQueue() { return mFlinger->mLocklessTransactionQueue; } - auto& getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; } + auto& getTransactionQueue() { return mFlinger->mTransactionHandler.mLocklessTransactionQueue; } + auto& getPendingTransactionQueue() { + return mFlinger->mTransactionHandler.mPendingTransactionQueues; + } + size_t getPendingTransactionCount() { + return mFlinger->mTransactionHandler.mPendingTransactionCount.load(); + } auto setTransactionState( const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& states, diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index b4030b3617..db438b70ab 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -14,7 +14,6 @@ * limitations under the License. */ - #undef LOG_TAG #define LOG_TAG "CompositionTest" @@ -22,12 +21,17 @@ #include <compositionengine/mock/DisplaySurface.h> #include <gmock/gmock.h> #include <gtest/gtest.h> +#include <gui/LayerState.h> #include <gui/SurfaceComposerClient.h> +#include <gui/fake/BufferData.h> #include <log/log.h> #include <ui/MockFence.h> #include <utils/String8.h> +#include <vector> +#include <binder/Binder.h> #include "TestableSurfaceFlinger.h" +#include "TransactionHandler.h" #include "mock/MockEventThread.h" #include "mock/MockVsyncController.h" @@ -78,6 +82,7 @@ public: mFlinger.setupScheduler(std::unique_ptr<mock::VsyncController>(mVsyncController), std::unique_ptr<mock::VSyncTracker>(mVSyncTracker), std::move(eventThread), std::move(sfEventThread)); + mFlinger.flinger()->addTransactionReadyFilters(); } TestableSurfaceFlinger mFlinger; @@ -314,7 +319,10 @@ public: ComposerState createComposerState(int layerId, sp<Fence> fence, uint64_t what) { ComposerState state; - state.state.bufferData = std::make_shared<BufferData>(); + state.state.bufferData = + std::make_shared<fake::BufferData>(/* bufferId */ 123L, /* width */ 1, + /* height */ 2, /* pixelFormat */ 0, + /* outUsage */ 0); state.state.bufferData->acquireFence = std::move(fence); state.state.layerId = layerId; state.state.surface = @@ -361,7 +369,7 @@ public: } mFlinger.flushTransactionQueues(); EXPECT_TRUE(mFlinger.getTransactionQueue().isEmpty()); - EXPECT_EQ(expectedTransactionsPending, mFlinger.getPendingTransactionQueue().size()); + EXPECT_EQ(expectedTransactionsPending, mFlinger.getPendingTransactionCount()); } }; @@ -413,7 +421,9 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBu { createComposerState(kLayerId, fence(Fence::Status::Unsignaled), - layer_state_t::eCropChanged), + layer_state_t::eCropChanged | + layer_state_t:: + eBufferChanged), }); setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } @@ -536,41 +546,6 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, kExpectedTransactionsPending); } -TEST_F(LatchUnsignaledAutoSingleLayerTest, UnsignaledNotAppliedWhenThereAreSignaled_SignaledFirst) { - const sp<IBinder> kApplyToken1 = - IInterface::asBinder(TransactionCompletedListener::getIInstance()); - const sp<IBinder> kApplyToken2 = sp<BBinder>::make(); - const sp<IBinder> kApplyToken3 = sp<BBinder>::make(); - const auto kLayerId1 = 1; - const auto kLayerId2 = 2; - const auto kExpectedTransactionsPending = 1u; - - const auto signaledTransaction = - createTransactionInfo(kApplyToken1, - { - createComposerState(kLayerId1, - fence(Fence::Status::Signaled), - layer_state_t::eBufferChanged), - }); - const auto signaledTransaction2 = - createTransactionInfo(kApplyToken2, - { - createComposerState(kLayerId1, - fence(Fence::Status::Signaled), - layer_state_t::eBufferChanged), - }); - const auto unsignaledTransaction = - createTransactionInfo(kApplyToken3, - { - createComposerState(kLayerId2, - fence(Fence::Status::Unsignaled), - layer_state_t::eBufferChanged), - }); - - setTransactionStates({signaledTransaction, signaledTransaction2, unsignaledTransaction}, - kExpectedTransactionsPending); -} - TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsTransactionInTheQueueSameApplyToken) { const sp<IBinder> kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); @@ -798,7 +773,7 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheUnsignaledTheQueue) { IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsPending = 1u; + const auto kExpectedTransactionsPending = 2u; const auto unsignaledTransaction = createTransactionInfo(kApplyToken, @@ -1004,4 +979,16 @@ TEST_F(LatchUnsignaledAlwaysTest, LatchUnsignaledWhenEarlyOffset) { setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } +TEST(TransactionHandlerTest, QueueTransaction) { + TransactionHandler handler; + TransactionState transaction; + transaction.applyToken = sp<BBinder>::make(); + transaction.id = 42; + handler.queueTransaction(std::move(transaction)); + std::vector<TransactionState> transactionsReadyToBeApplied = handler.flushTransactions(); + + EXPECT_EQ(transactionsReadyToBeApplied.size(), 1u); + EXPECT_EQ(transactionsReadyToBeApplied.front().id, 42u); +} + } // namespace android |