summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
author Matt Buckley <mattbuckley@google.com> 2023-09-22 22:17:29 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-09-22 22:17:29 +0000
commitd9b21f6fa512b5517aee81204b7ed76183af0514 (patch)
treeee6552034fe1e872b6bff85d9b788359ceb92f39 /libs
parentc914c49cb46a9125a807b4123626920307f1e241 (diff)
parent237bb3855ca87f653cea1ae0cee8992102e6b47c (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.bp1
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp13
-rw-r--r--libs/hwui/renderthread/CanvasContext.h2
-rw-r--r--libs/hwui/renderthread/HintSessionWrapper.cpp121
-rw-r--r--libs/hwui/renderthread/HintSessionWrapper.h34
-rw-r--r--libs/hwui/tests/unit/HintSessionWrapperTests.cpp287
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