diff options
author | 2023-09-22 22:17:29 +0000 | |
---|---|---|
committer | 2023-09-22 22:17:29 +0000 | |
commit | d9b21f6fa512b5517aee81204b7ed76183af0514 (patch) | |
tree | ee6552034fe1e872b6bff85d9b788359ceb92f39 /libs | |
parent | c914c49cb46a9125a807b4123626920307f1e241 (diff) | |
parent | 237bb3855ca87f653cea1ae0cee8992102e6b47c (diff) |
Merge changes Icc35293f,I05ed15cd into udc-qpr-dev
* changes:
Mitigation for mass GC deletion
Add unit tests for HintSessionWrapper
Diffstat (limited to 'libs')
-rw-r--r-- | libs/hwui/Android.bp | 1 | ||||
-rw-r--r-- | libs/hwui/renderthread/CanvasContext.cpp | 13 | ||||
-rw-r--r-- | libs/hwui/renderthread/CanvasContext.h | 2 | ||||
-rw-r--r-- | libs/hwui/renderthread/HintSessionWrapper.cpp | 121 | ||||
-rw-r--r-- | libs/hwui/renderthread/HintSessionWrapper.h | 34 | ||||
-rw-r--r-- | libs/hwui/tests/unit/HintSessionWrapperTests.cpp | 287 |
6 files changed, 385 insertions, 73 deletions
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index db581471e2ca..c87c15669a09 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -717,6 +717,7 @@ cc_test { "tests/unit/EglManagerTests.cpp", "tests/unit/FatVectorTests.cpp", "tests/unit/GraphicsStatsServiceTests.cpp", + "tests/unit/HintSessionWrapperTests.cpp", "tests/unit/JankTrackerTests.cpp", "tests/unit/FrameMetricsReporterTests.cpp", "tests/unit/LayerUpdateQueueTests.cpp", diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index f5b3ca602469..310e39e1d0a2 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -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); rootRenderNode->makeRoot(); mRenderNodes.emplace_back(rootRenderNode); @@ -160,6 +160,7 @@ void CanvasContext::destroy() { destroyHardwareResources(); mAnimationContext->destroy(); mRenderThread.cacheManager().onContextStopped(this); + mHintSessionWrapper->delayedDestroy(mRenderThread, 2_s, mHintSessionWrapper); } static void setBufferCount(ANativeWindow* window) { @@ -739,7 +740,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); @@ -747,7 +748,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) { int64_t actualDuration = frameDuration - (std::min(syncDelayDuration, mLastDequeueBufferDuration)) - dequeueBufferDuration - idleDuration; - mHintSessionWrapper.reportActualWorkDuration(actualDuration); + mHintSessionWrapper->reportActualWorkDuration(actualDuration); } mLastDequeueBufferDuration = dequeueBufferDuration; @@ -1081,11 +1082,11 @@ void CanvasContext::prepareSurfaceControlForWebview() { } void CanvasContext::sendLoadResetHint() { - mHintSessionWrapper.sendLoadResetHint(); + mHintSessionWrapper->sendLoadResetHint(); } void CanvasContext::sendLoadIncreaseHint() { - mHintSessionWrapper.sendLoadIncreaseHint(); + mHintSessionWrapper->sendLoadIncreaseHint(); } void CanvasContext::setSyncDelayDuration(nsecs_t duration) { @@ -1093,7 +1094,7 @@ void CanvasContext::setSyncDelayDuration(nsecs_t duration) { } void CanvasContext::startHintSession() { - mHintSessionWrapper.init(); + mHintSessionWrapper->init(); } bool CanvasContext::shouldDither() { diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 32ac5af94c14..10a4afb23d35 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -359,7 +359,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; diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp index 1f338ee523eb..d1ebe6d9f576 100644 --- a/libs/hwui/renderthread/HintSessionWrapper.cpp +++ b/libs/hwui/renderthread/HintSessionWrapper.cpp @@ -24,6 +24,7 @@ #include <vector> #include "../Properties.h" +#include "RenderThread.h" #include "thread/CommonPool.h" using namespace std::chrono_literals; @@ -32,76 +33,42 @@ namespace android { namespace uirenderer { namespace renderthread { -namespace { +#define BIND_APH_METHOD(name) \ + name = (decltype(name))dlsym(handle_, "APerformanceHint_" #name); \ + LOG_ALWAYS_FATAL_IF(name == nullptr, "Failed to find required symbol APerformanceHint_" #name) -typedef APerformanceHintManager* (*APH_getManager)(); -typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*, - size_t, int64_t); -typedef void (*APH_closeSession)(APerformanceHintSession* session); -typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t); -typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t); -typedef void (*APH_sendHint)(APerformanceHintSession* session, int32_t); - -bool gAPerformanceHintBindingInitialized = false; -APH_getManager gAPH_getManagerFn = nullptr; -APH_createSession gAPH_createSessionFn = nullptr; -APH_closeSession gAPH_closeSessionFn = nullptr; -APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr; -APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr; -APH_sendHint gAPH_sendHintFn = nullptr; - -void ensureAPerformanceHintBindingInitialized() { - if (gAPerformanceHintBindingInitialized) return; +void HintSessionWrapper::HintSessionBinding::init() { + if (mInitialized) return; void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!"); - gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager"); - LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr, - "Failed to find required symbol APerformanceHint_getManager!"); - - gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession"); - LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr, - "Failed to find required symbol APerformanceHint_createSession!"); - - gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession"); - LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr, - "Failed to find required symbol APerformanceHint_closeSession!"); - - gAPH_updateTargetWorkDurationFn = (APH_updateTargetWorkDuration)dlsym( - handle_, "APerformanceHint_updateTargetWorkDuration"); - LOG_ALWAYS_FATAL_IF( - gAPH_updateTargetWorkDurationFn == nullptr, - "Failed to find required symbol APerformanceHint_updateTargetWorkDuration!"); + BIND_APH_METHOD(getManager); + BIND_APH_METHOD(createSession); + BIND_APH_METHOD(closeSession); + BIND_APH_METHOD(updateTargetWorkDuration); + BIND_APH_METHOD(reportActualWorkDuration); + BIND_APH_METHOD(sendHint); - gAPH_reportActualWorkDurationFn = (APH_reportActualWorkDuration)dlsym( - handle_, "APerformanceHint_reportActualWorkDuration"); - LOG_ALWAYS_FATAL_IF( - gAPH_reportActualWorkDurationFn == nullptr, - "Failed to find required symbol APerformanceHint_reportActualWorkDuration!"); - - gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint"); - LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr, - "Failed to find required symbol APerformanceHint_sendHint!"); - - gAPerformanceHintBindingInitialized = true; + mInitialized = true; } -} // namespace - HintSessionWrapper::HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId) - : mUiThreadId(uiThreadId), mRenderThreadId(renderThreadId) {} + : mUiThreadId(uiThreadId) + , mRenderThreadId(renderThreadId) + , mBinding(std::make_shared<HintSessionBinding>()) {} HintSessionWrapper::~HintSessionWrapper() { destroy(); } void HintSessionWrapper::destroy() { - if (mHintSessionFuture.valid()) { - mHintSession = mHintSessionFuture.get(); + if (mHintSessionFuture.has_value()) { + mHintSession = mHintSessionFuture->get(); + mHintSessionFuture = std::nullopt; } if (mHintSession) { - gAPH_closeSessionFn(mHintSession); + mBinding->closeSession(mHintSession); mSessionValid = true; mHintSession = nullptr; } @@ -109,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; @@ -133,9 +100,9 @@ bool HintSessionWrapper::init() { // Assume that if we return before the end, it broke mSessionValid = false; - ensureAPerformanceHintBindingInitialized(); + mBinding->init(); - APerformanceHintManager* manager = gAPH_getManagerFn(); + APerformanceHintManager* manager = mBinding->getManager(); if (!manager) return false; std::vector<pid_t> tids = CommonPool::getThreadIds(); @@ -145,8 +112,9 @@ bool HintSessionWrapper::init() { // Use a placeholder target value to initialize, // this will always be replaced elsewhere before it gets used int64_t defaultTargetDurationNanos = 16666667; - mHintSessionFuture = CommonPool::async([=, tids = std::move(tids)] { - return gAPH_createSessionFn(manager, tids.data(), tids.size(), defaultTargetDurationNanos); + mHintSessionFuture = CommonPool::async([=, this, tids = std::move(tids)] { + return mBinding->createSession(manager, tids.data(), tids.size(), + defaultTargetDurationNanos); }); return false; } @@ -158,7 +126,7 @@ void HintSessionWrapper::updateTargetWorkDuration(long targetWorkDurationNanos) targetWorkDurationNanos > kSanityCheckLowerBound && targetWorkDurationNanos < kSanityCheckUpperBound) { mLastTargetWorkDuration = targetWorkDurationNanos; - gAPH_updateTargetWorkDurationFn(mHintSession, targetWorkDurationNanos); + mBinding->updateTargetWorkDuration(mHintSession, targetWorkDurationNanos); } mLastFrameNotification = systemTime(); } @@ -168,8 +136,9 @@ void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) { mResetsSinceLastReport = 0; if (actualDurationNanos > kSanityCheckLowerBound && actualDurationNanos < kSanityCheckUpperBound) { - gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos); + mBinding->reportActualWorkDuration(mHintSession, actualDurationNanos); } + mLastFrameNotification = systemTime(); } void HintSessionWrapper::sendLoadResetHint() { @@ -179,14 +148,36 @@ void HintSessionWrapper::sendLoadResetHint() { if (now - mLastFrameNotification > kResetHintTimeout && mResetsSinceLastReport <= kMaxResetsSinceLastReport) { ++mResetsSinceLastReport; - gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_RESET)); + mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_RESET)); } mLastFrameNotification = now; } void HintSessionWrapper::sendLoadIncreaseHint() { if (!init()) return; - gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_UP)); + 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 */ diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h index bdb9959b1ca7..36e91ea33c75 100644 --- a/libs/hwui/renderthread/HintSessionWrapper.h +++ b/libs/hwui/renderthread/HintSessionWrapper.h @@ -19,6 +19,7 @@ #include <android/performance_hint.h> #include <future> +#include <optional> #include "utils/TimeUtils.h" @@ -27,8 +28,12 @@ namespace uirenderer { namespace renderthread { +class RenderThread; + class HintSessionWrapper { public: + friend class HintSessionWrapperTests; + HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId); ~HintSessionWrapper(); @@ -38,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; @@ -55,6 +65,28 @@ private: static constexpr nsecs_t kResetHintTimeout = 100_ms; static constexpr int64_t kSanityCheckLowerBound = 100_us; static constexpr int64_t kSanityCheckUpperBound = 10_s; + + // Allows easier stub when testing + class HintSessionBinding { + public: + virtual ~HintSessionBinding() = default; + virtual void init(); + APerformanceHintManager* (*getManager)(); + APerformanceHintSession* (*createSession)(APerformanceHintManager* manager, + const int32_t* tids, size_t tidCount, + int64_t defaultTarget) = nullptr; + void (*closeSession)(APerformanceHintSession* session) = nullptr; + void (*updateTargetWorkDuration)(APerformanceHintSession* session, + int64_t targetDuration) = nullptr; + void (*reportActualWorkDuration)(APerformanceHintSession* session, + int64_t actualDuration) = nullptr; + void (*sendHint)(APerformanceHintSession* session, int32_t hintId) = nullptr; + + private: + bool mInitialized = false; + }; + + std::shared_ptr<HintSessionBinding> mBinding; }; } /* namespace renderthread */ diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp new file mode 100644 index 000000000000..5a10f4f959aa --- /dev/null +++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2023 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. + */ + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <private/performance_hint_private.h> +#include <renderthread/HintSessionWrapper.h> +#include <utils/Log.h> + +#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); +int uiThreadId = 1; +int renderThreadId = 2; + +namespace android::uirenderer::renderthread { + +class HintSessionWrapperTests : public testing::Test { +public: + void SetUp() override; + void TearDown() override; + +protected: + std::shared_ptr<HintSessionWrapper> mWrapper; + + std::promise<int> blockDestroyCallUntil; + std::promise<int> waitForDestroyFinished; + + class MockHintSessionBinding : public HintSessionWrapper::HintSessionBinding { + public: + void init() override; + + MOCK_METHOD(APerformanceHintManager*, fakeGetManager, ()); + MOCK_METHOD(APerformanceHintSession*, fakeCreateSession, + (APerformanceHintManager*, const int32_t*, size_t, int64_t)); + MOCK_METHOD(void, fakeCloseSession, (APerformanceHintSession*)); + 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, + const int32_t* ids, size_t idsSize, + 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) { + std::this_thread::sleep_for(50ms); + return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget); + } + static void stubCloseSession(APerformanceHintSession* session) { + sMockBinding->fakeCloseSession(session); + }; + static void stubUpdateTargetWorkDuration(APerformanceHintSession* session, + int64_t workDuration) { + sMockBinding->fakeUpdateTargetWorkDuration(session, workDuration); + } + static void stubReportActualWorkDuration(APerformanceHintSession* session, + int64_t workDuration) { + sMockBinding->fakeReportActualWorkDuration(session, workDuration); + } + static void stubSendHint(APerformanceHintSession* session, int32_t hintId) { + sMockBinding->fakeSendHint(session, hintId); + }; + 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> + HintSessionWrapperTests::sMockBinding; + +void HintSessionWrapperTests::SetUp() { + // Pretend it's supported even if we're in an emulator + Properties::useHintManager = true; + sMockBinding = std::make_shared<NiceMock<MockHintSessionBinding>>(); + mWrapper = std::make_shared<HintSessionWrapper>(uiThreadId, renderThreadId); + mWrapper->mBinding = sMockBinding; + EXPECT_CALL(*sMockBinding, fakeGetManager).WillOnce(Return(managerPtr)); + ON_CALL(*sMockBinding, fakeCreateSession).WillByDefault(Return(sessionPtr)); +} + +void HintSessionWrapperTests::MockHintSessionBinding::init() { + sMockBinding->getManager = &stubGetManager; + if (sMockBinding->createSession == nullptr) { + sMockBinding->createSession = &stubCreateSession; + } + sMockBinding->closeSession = &stubCloseSession; + sMockBinding->updateTargetWorkDuration = &stubUpdateTargetWorkDuration; + sMockBinding->reportActualWorkDuration = &stubReportActualWorkDuration; + sMockBinding->sendHint = &stubSendHint; +} + +void HintSessionWrapperTests::TearDown() { + // Ensure that anything running on RT is completely finished + mWrapper = nullptr; + sMockBinding = nullptr; +} + +TEST_F(HintSessionWrapperTests, destructorClosesBackgroundSession) { + EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1); + sMockBinding->createSession = stubSlowCreateSession; + mWrapper->init(); + mWrapper = nullptr; + Mock::VerifyAndClearExpectations(sMockBinding.get()); +} + +TEST_F(HintSessionWrapperTests, sessionInitializesCorrectly) { + EXPECT_CALL(*sMockBinding, fakeCreateSession(managerPtr, _, Gt(1), _)).Times(1); + mWrapper->init(); + waitForWrapperReady(); +} + +TEST_F(HintSessionWrapperTests, loadUpHintsSendCorrectly) { + EXPECT_CALL(*sMockBinding, + fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP))) + .Times(1); + mWrapper->init(); + waitForWrapperReady(); + mWrapper->sendLoadIncreaseHint(); +} + +TEST_F(HintSessionWrapperTests, loadResetHintsSendCorrectly) { + EXPECT_CALL(*sMockBinding, + fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_RESET))) + .Times(1); + mWrapper->init(); + waitForWrapperReady(); + 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 |