Loading libs/hwui/renderthread/CanvasContext.cpp +7 −6 Original line number Diff line number Diff line Loading @@ -123,7 +123,7 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* , mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos()) , mContentDrawBounds(0, 0, 0, 0) , mRenderPipeline(std::move(renderPipeline)) , mHintSessionWrapper(uiThreadId, renderThreadId) { , mHintSessionWrapper(std::make_shared<HintSessionWrapper>(uiThreadId, renderThreadId)) { mRenderThread.cacheManager().registerCanvasContext(this); mRenderThread.renderState().registerContextCallback(this); rootRenderNode->makeRoot(); Loading Loading @@ -162,6 +162,7 @@ void CanvasContext::destroy() { destroyHardwareResources(); mAnimationContext->destroy(); mRenderThread.cacheManager().onContextStopped(this); mHintSessionWrapper->delayedDestroy(mRenderThread, 2_s, mHintSessionWrapper); } static void setBufferCount(ANativeWindow* window) { Loading Loading @@ -766,7 +767,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) { int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline); int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration); mHintSessionWrapper.updateTargetWorkDuration(frameDeadline - intendedVsync); mHintSessionWrapper->updateTargetWorkDuration(frameDeadline - intendedVsync); if (didDraw) { int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime); Loading @@ -774,7 +775,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) { int64_t actualDuration = frameDuration - (std::min(syncDelayDuration, mLastDequeueBufferDuration)) - dequeueBufferDuration - idleDuration; mHintSessionWrapper.reportActualWorkDuration(actualDuration); mHintSessionWrapper->reportActualWorkDuration(actualDuration); } mLastDequeueBufferDuration = dequeueBufferDuration; Loading Loading @@ -1112,11 +1113,11 @@ void CanvasContext::prepareSurfaceControlForWebview() { } void CanvasContext::sendLoadResetHint() { mHintSessionWrapper.sendLoadResetHint(); mHintSessionWrapper->sendLoadResetHint(); } void CanvasContext::sendLoadIncreaseHint() { mHintSessionWrapper.sendLoadIncreaseHint(); mHintSessionWrapper->sendLoadIncreaseHint(); } void CanvasContext::setSyncDelayDuration(nsecs_t duration) { Loading @@ -1124,7 +1125,7 @@ void CanvasContext::setSyncDelayDuration(nsecs_t duration) { } void CanvasContext::startHintSession() { mHintSessionWrapper.init(); mHintSessionWrapper->init(); } bool CanvasContext::shouldDither() { Loading libs/hwui/renderthread/CanvasContext.h +1 −1 Original line number Diff line number Diff line Loading @@ -363,7 +363,7 @@ private: std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback; std::function<void()> mPrepareSurfaceControlForWebviewCallback; HintSessionWrapper mHintSessionWrapper; std::shared_ptr<HintSessionWrapper> mHintSessionWrapper; nsecs_t mLastDequeueBufferDuration = 0; nsecs_t mSyncDelayDuration = 0; nsecs_t mIdleDuration = 0; Loading libs/hwui/renderthread/HintSessionWrapper.cpp +31 −6 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ #include <vector> #include "../Properties.h" #include "RenderThread.h" #include "thread/CommonPool.h" using namespace std::chrono_literals; Loading Loading @@ -62,8 +63,9 @@ HintSessionWrapper::~HintSessionWrapper() { } void HintSessionWrapper::destroy() { if (mHintSessionFuture.valid()) { mHintSession = mHintSessionFuture.get(); if (mHintSessionFuture.has_value()) { mHintSession = mHintSessionFuture->get(); mHintSessionFuture = std::nullopt; } if (mHintSession) { mBinding->closeSession(mHintSession); Loading @@ -74,12 +76,12 @@ void HintSessionWrapper::destroy() { bool HintSessionWrapper::init() { if (mHintSession != nullptr) return true; // If we're waiting for the session if (mHintSessionFuture.valid()) { if (mHintSessionFuture.has_value()) { // If the session is here if (mHintSessionFuture.wait_for(0s) == std::future_status::ready) { mHintSession = mHintSessionFuture.get(); if (mHintSessionFuture->wait_for(0s) == std::future_status::ready) { mHintSession = mHintSessionFuture->get(); mHintSessionFuture = std::nullopt; if (mHintSession != nullptr) { mSessionValid = true; return true; Loading Loading @@ -136,6 +138,7 @@ void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) { actualDurationNanos < kSanityCheckUpperBound) { mBinding->reportActualWorkDuration(mHintSession, actualDurationNanos); } mLastFrameNotification = systemTime(); } void HintSessionWrapper::sendLoadResetHint() { Loading @@ -153,6 +156,28 @@ void HintSessionWrapper::sendLoadResetHint() { void HintSessionWrapper::sendLoadIncreaseHint() { if (!init()) return; mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)); mLastFrameNotification = systemTime(); } bool HintSessionWrapper::alive() { return mHintSession != nullptr; } nsecs_t HintSessionWrapper::getLastUpdate() { return mLastFrameNotification; } // Requires passing in its shared_ptr since it shouldn't own a shared_ptr to itself void HintSessionWrapper::delayedDestroy(RenderThread& rt, nsecs_t delay, std::shared_ptr<HintSessionWrapper> wrapperPtr) { nsecs_t lastUpdate = wrapperPtr->getLastUpdate(); rt.queue().postDelayed(delay, [lastUpdate = lastUpdate, wrapper = wrapperPtr]() mutable { if (wrapper->getLastUpdate() == lastUpdate) { wrapper->destroy(); } // Ensure the shared_ptr is killed at the end of the method wrapper = nullptr; }); } } /* namespace renderthread */ Loading libs/hwui/renderthread/HintSessionWrapper.h +9 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ #include <android/performance_hint.h> #include <future> #include <optional> #include "utils/TimeUtils.h" Loading @@ -27,6 +28,8 @@ namespace uirenderer { namespace renderthread { class RenderThread; class HintSessionWrapper { public: friend class HintSessionWrapperTests; Loading @@ -40,10 +43,15 @@ public: void sendLoadIncreaseHint(); bool init(); void destroy(); bool alive(); nsecs_t getLastUpdate(); void delayedDestroy(renderthread::RenderThread& rt, nsecs_t delay, std::shared_ptr<HintSessionWrapper> wrapperPtr); private: APerformanceHintSession* mHintSession = nullptr; std::future<APerformanceHintSession*> mHintSessionFuture; // This needs to work concurrently for testing std::optional<std::shared_future<APerformanceHintSession*>> mHintSessionFuture; int mResetsSinceLastReport = 0; nsecs_t mLastFrameNotification = 0; Loading libs/hwui/tests/unit/HintSessionWrapperTests.cpp +137 −1 Original line number Diff line number Diff line Loading @@ -23,9 +23,11 @@ #include <chrono> #include "Properties.h" #include "tests/common/TestUtils.h" using namespace testing; using namespace std::chrono_literals; using namespace android::uirenderer::renderthread; APerformanceHintManager* managerPtr = reinterpret_cast<APerformanceHintManager*>(123); APerformanceHintSession* sessionPtr = reinterpret_cast<APerformanceHintSession*>(456); Loading @@ -42,6 +44,9 @@ public: protected: std::shared_ptr<HintSessionWrapper> mWrapper; std::promise<int> blockDestroyCallUntil; std::promise<int> waitForDestroyFinished; class MockHintSessionBinding : public HintSessionWrapper::HintSessionBinding { public: void init() override; Loading @@ -53,11 +58,17 @@ protected: MOCK_METHOD(void, fakeUpdateTargetWorkDuration, (APerformanceHintSession*, int64_t)); MOCK_METHOD(void, fakeReportActualWorkDuration, (APerformanceHintSession*, int64_t)); MOCK_METHOD(void, fakeSendHint, (APerformanceHintSession*, int32_t)); // Needs to be on the binding so it can be accessed from static methods std::promise<int> allowCreationToFinish; }; // Must be static so it can have function pointers we can point to with static methods static std::shared_ptr<MockHintSessionBinding> sMockBinding; static void allowCreationToFinish() { sMockBinding->allowCreationToFinish.set_value(1); } void allowDelayedDestructionToStart() { blockDestroyCallUntil.set_value(1); } void waitForDelayedDestructionToFinish() { waitForDestroyFinished.get_future().wait(); } // Must be static so we can point to them as normal fn pointers with HintSessionBinding static APerformanceHintManager* stubGetManager() { return sMockBinding->fakeGetManager(); }; static APerformanceHintSession* stubCreateSession(APerformanceHintManager* manager, Loading @@ -65,6 +76,12 @@ protected: int64_t initialTarget) { return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget); } static APerformanceHintSession* stubManagedCreateSession(APerformanceHintManager* manager, const int32_t* ids, size_t idsSize, int64_t initialTarget) { sMockBinding->allowCreationToFinish.get_future().wait(); return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget); } static APerformanceHintSession* stubSlowCreateSession(APerformanceHintManager* manager, const int32_t* ids, size_t idsSize, int64_t initialTarget) { Loading @@ -85,7 +102,21 @@ protected: static void stubSendHint(APerformanceHintSession* session, int32_t hintId) { sMockBinding->fakeSendHint(session, hintId); }; void waitForWrapperReady() { mWrapper->mHintSessionFuture.wait(); } void waitForWrapperReady() { if (mWrapper->mHintSessionFuture.has_value()) { mWrapper->mHintSessionFuture->wait(); } } void scheduleDelayedDestroyManaged() { TestUtils::runOnRenderThread([&](renderthread::RenderThread& rt) { // Guaranteed to be scheduled first, allows destruction to start rt.queue().postDelayed(0_ms, [&] { blockDestroyCallUntil.get_future().wait(); }); // Guaranteed to be scheduled second, destroys the session mWrapper->delayedDestroy(rt, 1_ms, mWrapper); // This is guaranteed to be queued after the destroy, signals that destruction is done rt.queue().postDelayed(1_ms, [&] { waitForDestroyFinished.set_value(1); }); }); } }; std::shared_ptr<HintSessionWrapperTests::MockHintSessionBinding> Loading Loading @@ -113,6 +144,7 @@ void HintSessionWrapperTests::MockHintSessionBinding::init() { } void HintSessionWrapperTests::TearDown() { // Ensure that anything running on RT is completely finished mWrapper = nullptr; sMockBinding = nullptr; } Loading @@ -122,6 +154,7 @@ TEST_F(HintSessionWrapperTests, destructorClosesBackgroundSession) { sMockBinding->createSession = stubSlowCreateSession; mWrapper->init(); mWrapper = nullptr; Mock::VerifyAndClearExpectations(sMockBinding.get()); } TEST_F(HintSessionWrapperTests, sessionInitializesCorrectly) { Loading @@ -148,4 +181,107 @@ TEST_F(HintSessionWrapperTests, loadResetHintsSendCorrectly) { mWrapper->sendLoadResetHint(); } TEST_F(HintSessionWrapperTests, delayedDeletionWorksCorrectlyAndOnlyClosesOnce) { EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1); mWrapper->init(); waitForWrapperReady(); // Init a second time just to ensure the wrapper grabs the promise value mWrapper->init(); EXPECT_EQ(mWrapper->alive(), true); // Schedule delayed destruction, allow it to run, and check when it's done scheduleDelayedDestroyManaged(); allowDelayedDestructionToStart(); waitForDelayedDestructionToFinish(); // Ensure it closed within the timeframe of the test Mock::VerifyAndClearExpectations(sMockBinding.get()); EXPECT_EQ(mWrapper->alive(), false); // If we then delete the wrapper, it shouldn't close the session again EXPECT_CALL(*sMockBinding, fakeCloseSession(_)).Times(0); mWrapper = nullptr; } TEST_F(HintSessionWrapperTests, delayedDeletionResolvesBeforeAsyncCreationFinishes) { // Here we test whether queueing delayedDestroy works while creation is still happening, if // creation happens after EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1); sMockBinding->createSession = &stubManagedCreateSession; // Start creating the session and destroying it at the same time mWrapper->init(); scheduleDelayedDestroyManaged(); // Allow destruction to happen first allowDelayedDestructionToStart(); // Make sure destruction has had time to happen std::this_thread::sleep_for(50ms); // Then, allow creation to finish after delayed destroy runs allowCreationToFinish(); // Wait for destruction to finish waitForDelayedDestructionToFinish(); // Ensure it closed within the timeframe of the test Mock::VerifyAndClearExpectations(sMockBinding.get()); EXPECT_EQ(mWrapper->alive(), false); } TEST_F(HintSessionWrapperTests, delayedDeletionResolvesAfterAsyncCreationFinishes) { // Here we test whether queueing delayedDestroy works while creation is still happening, if // creation happens before EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1); sMockBinding->createSession = &stubManagedCreateSession; // Start creating the session and destroying it at the same time mWrapper->init(); scheduleDelayedDestroyManaged(); // Allow creation to happen first allowCreationToFinish(); // Make sure creation has had time to happen waitForWrapperReady(); // Then allow destruction to happen after creation is done allowDelayedDestructionToStart(); // Wait for it to finish waitForDelayedDestructionToFinish(); // Ensure it closed within the timeframe of the test Mock::VerifyAndClearExpectations(sMockBinding.get()); EXPECT_EQ(mWrapper->alive(), false); } TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) { EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0); EXPECT_CALL(*sMockBinding, fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP))) .Times(1); mWrapper->init(); waitForWrapperReady(); // Init a second time just to grab the wrapper from the promise mWrapper->init(); EXPECT_EQ(mWrapper->alive(), true); // First schedule the deletion scheduleDelayedDestroyManaged(); // Then, send a hint to update the timestamp mWrapper->sendLoadIncreaseHint(); // Then, run the delayed deletion after sending the update allowDelayedDestructionToStart(); waitForDelayedDestructionToFinish(); // Ensure it didn't close within the timeframe of the test Mock::VerifyAndClearExpectations(sMockBinding.get()); EXPECT_EQ(mWrapper->alive(), true); } } // namespace android::uirenderer::renderthread No newline at end of file Loading
libs/hwui/renderthread/CanvasContext.cpp +7 −6 Original line number Diff line number Diff line Loading @@ -123,7 +123,7 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* , mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos()) , mContentDrawBounds(0, 0, 0, 0) , mRenderPipeline(std::move(renderPipeline)) , mHintSessionWrapper(uiThreadId, renderThreadId) { , mHintSessionWrapper(std::make_shared<HintSessionWrapper>(uiThreadId, renderThreadId)) { mRenderThread.cacheManager().registerCanvasContext(this); mRenderThread.renderState().registerContextCallback(this); rootRenderNode->makeRoot(); Loading Loading @@ -162,6 +162,7 @@ void CanvasContext::destroy() { destroyHardwareResources(); mAnimationContext->destroy(); mRenderThread.cacheManager().onContextStopped(this); mHintSessionWrapper->delayedDestroy(mRenderThread, 2_s, mHintSessionWrapper); } static void setBufferCount(ANativeWindow* window) { Loading Loading @@ -766,7 +767,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) { int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline); int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration); mHintSessionWrapper.updateTargetWorkDuration(frameDeadline - intendedVsync); mHintSessionWrapper->updateTargetWorkDuration(frameDeadline - intendedVsync); if (didDraw) { int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime); Loading @@ -774,7 +775,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) { int64_t actualDuration = frameDuration - (std::min(syncDelayDuration, mLastDequeueBufferDuration)) - dequeueBufferDuration - idleDuration; mHintSessionWrapper.reportActualWorkDuration(actualDuration); mHintSessionWrapper->reportActualWorkDuration(actualDuration); } mLastDequeueBufferDuration = dequeueBufferDuration; Loading Loading @@ -1112,11 +1113,11 @@ void CanvasContext::prepareSurfaceControlForWebview() { } void CanvasContext::sendLoadResetHint() { mHintSessionWrapper.sendLoadResetHint(); mHintSessionWrapper->sendLoadResetHint(); } void CanvasContext::sendLoadIncreaseHint() { mHintSessionWrapper.sendLoadIncreaseHint(); mHintSessionWrapper->sendLoadIncreaseHint(); } void CanvasContext::setSyncDelayDuration(nsecs_t duration) { Loading @@ -1124,7 +1125,7 @@ void CanvasContext::setSyncDelayDuration(nsecs_t duration) { } void CanvasContext::startHintSession() { mHintSessionWrapper.init(); mHintSessionWrapper->init(); } bool CanvasContext::shouldDither() { Loading
libs/hwui/renderthread/CanvasContext.h +1 −1 Original line number Diff line number Diff line Loading @@ -363,7 +363,7 @@ private: std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback; std::function<void()> mPrepareSurfaceControlForWebviewCallback; HintSessionWrapper mHintSessionWrapper; std::shared_ptr<HintSessionWrapper> mHintSessionWrapper; nsecs_t mLastDequeueBufferDuration = 0; nsecs_t mSyncDelayDuration = 0; nsecs_t mIdleDuration = 0; Loading
libs/hwui/renderthread/HintSessionWrapper.cpp +31 −6 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ #include <vector> #include "../Properties.h" #include "RenderThread.h" #include "thread/CommonPool.h" using namespace std::chrono_literals; Loading Loading @@ -62,8 +63,9 @@ HintSessionWrapper::~HintSessionWrapper() { } void HintSessionWrapper::destroy() { if (mHintSessionFuture.valid()) { mHintSession = mHintSessionFuture.get(); if (mHintSessionFuture.has_value()) { mHintSession = mHintSessionFuture->get(); mHintSessionFuture = std::nullopt; } if (mHintSession) { mBinding->closeSession(mHintSession); Loading @@ -74,12 +76,12 @@ void HintSessionWrapper::destroy() { bool HintSessionWrapper::init() { if (mHintSession != nullptr) return true; // If we're waiting for the session if (mHintSessionFuture.valid()) { if (mHintSessionFuture.has_value()) { // If the session is here if (mHintSessionFuture.wait_for(0s) == std::future_status::ready) { mHintSession = mHintSessionFuture.get(); if (mHintSessionFuture->wait_for(0s) == std::future_status::ready) { mHintSession = mHintSessionFuture->get(); mHintSessionFuture = std::nullopt; if (mHintSession != nullptr) { mSessionValid = true; return true; Loading Loading @@ -136,6 +138,7 @@ void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) { actualDurationNanos < kSanityCheckUpperBound) { mBinding->reportActualWorkDuration(mHintSession, actualDurationNanos); } mLastFrameNotification = systemTime(); } void HintSessionWrapper::sendLoadResetHint() { Loading @@ -153,6 +156,28 @@ void HintSessionWrapper::sendLoadResetHint() { void HintSessionWrapper::sendLoadIncreaseHint() { if (!init()) return; mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)); mLastFrameNotification = systemTime(); } bool HintSessionWrapper::alive() { return mHintSession != nullptr; } nsecs_t HintSessionWrapper::getLastUpdate() { return mLastFrameNotification; } // Requires passing in its shared_ptr since it shouldn't own a shared_ptr to itself void HintSessionWrapper::delayedDestroy(RenderThread& rt, nsecs_t delay, std::shared_ptr<HintSessionWrapper> wrapperPtr) { nsecs_t lastUpdate = wrapperPtr->getLastUpdate(); rt.queue().postDelayed(delay, [lastUpdate = lastUpdate, wrapper = wrapperPtr]() mutable { if (wrapper->getLastUpdate() == lastUpdate) { wrapper->destroy(); } // Ensure the shared_ptr is killed at the end of the method wrapper = nullptr; }); } } /* namespace renderthread */ Loading
libs/hwui/renderthread/HintSessionWrapper.h +9 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ #include <android/performance_hint.h> #include <future> #include <optional> #include "utils/TimeUtils.h" Loading @@ -27,6 +28,8 @@ namespace uirenderer { namespace renderthread { class RenderThread; class HintSessionWrapper { public: friend class HintSessionWrapperTests; Loading @@ -40,10 +43,15 @@ public: void sendLoadIncreaseHint(); bool init(); void destroy(); bool alive(); nsecs_t getLastUpdate(); void delayedDestroy(renderthread::RenderThread& rt, nsecs_t delay, std::shared_ptr<HintSessionWrapper> wrapperPtr); private: APerformanceHintSession* mHintSession = nullptr; std::future<APerformanceHintSession*> mHintSessionFuture; // This needs to work concurrently for testing std::optional<std::shared_future<APerformanceHintSession*>> mHintSessionFuture; int mResetsSinceLastReport = 0; nsecs_t mLastFrameNotification = 0; Loading
libs/hwui/tests/unit/HintSessionWrapperTests.cpp +137 −1 Original line number Diff line number Diff line Loading @@ -23,9 +23,11 @@ #include <chrono> #include "Properties.h" #include "tests/common/TestUtils.h" using namespace testing; using namespace std::chrono_literals; using namespace android::uirenderer::renderthread; APerformanceHintManager* managerPtr = reinterpret_cast<APerformanceHintManager*>(123); APerformanceHintSession* sessionPtr = reinterpret_cast<APerformanceHintSession*>(456); Loading @@ -42,6 +44,9 @@ public: protected: std::shared_ptr<HintSessionWrapper> mWrapper; std::promise<int> blockDestroyCallUntil; std::promise<int> waitForDestroyFinished; class MockHintSessionBinding : public HintSessionWrapper::HintSessionBinding { public: void init() override; Loading @@ -53,11 +58,17 @@ protected: MOCK_METHOD(void, fakeUpdateTargetWorkDuration, (APerformanceHintSession*, int64_t)); MOCK_METHOD(void, fakeReportActualWorkDuration, (APerformanceHintSession*, int64_t)); MOCK_METHOD(void, fakeSendHint, (APerformanceHintSession*, int32_t)); // Needs to be on the binding so it can be accessed from static methods std::promise<int> allowCreationToFinish; }; // Must be static so it can have function pointers we can point to with static methods static std::shared_ptr<MockHintSessionBinding> sMockBinding; static void allowCreationToFinish() { sMockBinding->allowCreationToFinish.set_value(1); } void allowDelayedDestructionToStart() { blockDestroyCallUntil.set_value(1); } void waitForDelayedDestructionToFinish() { waitForDestroyFinished.get_future().wait(); } // Must be static so we can point to them as normal fn pointers with HintSessionBinding static APerformanceHintManager* stubGetManager() { return sMockBinding->fakeGetManager(); }; static APerformanceHintSession* stubCreateSession(APerformanceHintManager* manager, Loading @@ -65,6 +76,12 @@ protected: int64_t initialTarget) { return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget); } static APerformanceHintSession* stubManagedCreateSession(APerformanceHintManager* manager, const int32_t* ids, size_t idsSize, int64_t initialTarget) { sMockBinding->allowCreationToFinish.get_future().wait(); return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget); } static APerformanceHintSession* stubSlowCreateSession(APerformanceHintManager* manager, const int32_t* ids, size_t idsSize, int64_t initialTarget) { Loading @@ -85,7 +102,21 @@ protected: static void stubSendHint(APerformanceHintSession* session, int32_t hintId) { sMockBinding->fakeSendHint(session, hintId); }; void waitForWrapperReady() { mWrapper->mHintSessionFuture.wait(); } void waitForWrapperReady() { if (mWrapper->mHintSessionFuture.has_value()) { mWrapper->mHintSessionFuture->wait(); } } void scheduleDelayedDestroyManaged() { TestUtils::runOnRenderThread([&](renderthread::RenderThread& rt) { // Guaranteed to be scheduled first, allows destruction to start rt.queue().postDelayed(0_ms, [&] { blockDestroyCallUntil.get_future().wait(); }); // Guaranteed to be scheduled second, destroys the session mWrapper->delayedDestroy(rt, 1_ms, mWrapper); // This is guaranteed to be queued after the destroy, signals that destruction is done rt.queue().postDelayed(1_ms, [&] { waitForDestroyFinished.set_value(1); }); }); } }; std::shared_ptr<HintSessionWrapperTests::MockHintSessionBinding> Loading Loading @@ -113,6 +144,7 @@ void HintSessionWrapperTests::MockHintSessionBinding::init() { } void HintSessionWrapperTests::TearDown() { // Ensure that anything running on RT is completely finished mWrapper = nullptr; sMockBinding = nullptr; } Loading @@ -122,6 +154,7 @@ TEST_F(HintSessionWrapperTests, destructorClosesBackgroundSession) { sMockBinding->createSession = stubSlowCreateSession; mWrapper->init(); mWrapper = nullptr; Mock::VerifyAndClearExpectations(sMockBinding.get()); } TEST_F(HintSessionWrapperTests, sessionInitializesCorrectly) { Loading @@ -148,4 +181,107 @@ TEST_F(HintSessionWrapperTests, loadResetHintsSendCorrectly) { mWrapper->sendLoadResetHint(); } TEST_F(HintSessionWrapperTests, delayedDeletionWorksCorrectlyAndOnlyClosesOnce) { EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1); mWrapper->init(); waitForWrapperReady(); // Init a second time just to ensure the wrapper grabs the promise value mWrapper->init(); EXPECT_EQ(mWrapper->alive(), true); // Schedule delayed destruction, allow it to run, and check when it's done scheduleDelayedDestroyManaged(); allowDelayedDestructionToStart(); waitForDelayedDestructionToFinish(); // Ensure it closed within the timeframe of the test Mock::VerifyAndClearExpectations(sMockBinding.get()); EXPECT_EQ(mWrapper->alive(), false); // If we then delete the wrapper, it shouldn't close the session again EXPECT_CALL(*sMockBinding, fakeCloseSession(_)).Times(0); mWrapper = nullptr; } TEST_F(HintSessionWrapperTests, delayedDeletionResolvesBeforeAsyncCreationFinishes) { // Here we test whether queueing delayedDestroy works while creation is still happening, if // creation happens after EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1); sMockBinding->createSession = &stubManagedCreateSession; // Start creating the session and destroying it at the same time mWrapper->init(); scheduleDelayedDestroyManaged(); // Allow destruction to happen first allowDelayedDestructionToStart(); // Make sure destruction has had time to happen std::this_thread::sleep_for(50ms); // Then, allow creation to finish after delayed destroy runs allowCreationToFinish(); // Wait for destruction to finish waitForDelayedDestructionToFinish(); // Ensure it closed within the timeframe of the test Mock::VerifyAndClearExpectations(sMockBinding.get()); EXPECT_EQ(mWrapper->alive(), false); } TEST_F(HintSessionWrapperTests, delayedDeletionResolvesAfterAsyncCreationFinishes) { // Here we test whether queueing delayedDestroy works while creation is still happening, if // creation happens before EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1); sMockBinding->createSession = &stubManagedCreateSession; // Start creating the session and destroying it at the same time mWrapper->init(); scheduleDelayedDestroyManaged(); // Allow creation to happen first allowCreationToFinish(); // Make sure creation has had time to happen waitForWrapperReady(); // Then allow destruction to happen after creation is done allowDelayedDestructionToStart(); // Wait for it to finish waitForDelayedDestructionToFinish(); // Ensure it closed within the timeframe of the test Mock::VerifyAndClearExpectations(sMockBinding.get()); EXPECT_EQ(mWrapper->alive(), false); } TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) { EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0); EXPECT_CALL(*sMockBinding, fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP))) .Times(1); mWrapper->init(); waitForWrapperReady(); // Init a second time just to grab the wrapper from the promise mWrapper->init(); EXPECT_EQ(mWrapper->alive(), true); // First schedule the deletion scheduleDelayedDestroyManaged(); // Then, send a hint to update the timestamp mWrapper->sendLoadIncreaseHint(); // Then, run the delayed deletion after sending the update allowDelayedDestructionToStart(); waitForDelayedDestructionToFinish(); // Ensure it didn't close within the timeframe of the test Mock::VerifyAndClearExpectations(sMockBinding.get()); EXPECT_EQ(mWrapper->alive(), true); } } // namespace android::uirenderer::renderthread No newline at end of file