Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0daae6aa authored by Matt Buckley's avatar Matt Buckley
Browse files

Mitigation for mass GC deletion

Delete sessions after 2 seconds from when CC::destroy() is called, to
ensure they get the chance to be re-used without being destroyed but
still get destroyed if they aren't re-used.

This also adds several new unit tests around the functionality to ensure
its thread-safety.

Test: hwuitest
Bug: 300360668
Change-Id: Icc35293ff49e14dc3599fc12d153f70acd29042a
parent 0c668368
Loading
Loading
Loading
Loading
+7 −6
Original line number Original line Diff line number Diff line
@@ -123,7 +123,7 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode*
        , mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos())
        , mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos())
        , mContentDrawBounds(0, 0, 0, 0)
        , mContentDrawBounds(0, 0, 0, 0)
        , mRenderPipeline(std::move(renderPipeline))
        , mRenderPipeline(std::move(renderPipeline))
        , mHintSessionWrapper(uiThreadId, renderThreadId) {
        , mHintSessionWrapper(std::make_shared<HintSessionWrapper>(uiThreadId, renderThreadId)) {
    mRenderThread.cacheManager().registerCanvasContext(this);
    mRenderThread.cacheManager().registerCanvasContext(this);
    mRenderThread.renderState().registerContextCallback(this);
    mRenderThread.renderState().registerContextCallback(this);
    rootRenderNode->makeRoot();
    rootRenderNode->makeRoot();
@@ -162,6 +162,7 @@ void CanvasContext::destroy() {
    destroyHardwareResources();
    destroyHardwareResources();
    mAnimationContext->destroy();
    mAnimationContext->destroy();
    mRenderThread.cacheManager().onContextStopped(this);
    mRenderThread.cacheManager().onContextStopped(this);
    mHintSessionWrapper->delayedDestroy(mRenderThread, 2_s, mHintSessionWrapper);
}
}


static void setBufferCount(ANativeWindow* window) {
static void setBufferCount(ANativeWindow* window) {
@@ -766,7 +767,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) {
    int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline);
    int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline);
    int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
    int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);


    mHintSessionWrapper.updateTargetWorkDuration(frameDeadline - intendedVsync);
    mHintSessionWrapper->updateTargetWorkDuration(frameDeadline - intendedVsync);


    if (didDraw) {
    if (didDraw) {
        int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
        int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
@@ -774,7 +775,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) {
        int64_t actualDuration = frameDuration -
        int64_t actualDuration = frameDuration -
                                 (std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
                                 (std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
                                 dequeueBufferDuration - idleDuration;
                                 dequeueBufferDuration - idleDuration;
        mHintSessionWrapper.reportActualWorkDuration(actualDuration);
        mHintSessionWrapper->reportActualWorkDuration(actualDuration);
    }
    }


    mLastDequeueBufferDuration = dequeueBufferDuration;
    mLastDequeueBufferDuration = dequeueBufferDuration;
@@ -1112,11 +1113,11 @@ void CanvasContext::prepareSurfaceControlForWebview() {
}
}


void CanvasContext::sendLoadResetHint() {
void CanvasContext::sendLoadResetHint() {
    mHintSessionWrapper.sendLoadResetHint();
    mHintSessionWrapper->sendLoadResetHint();
}
}


void CanvasContext::sendLoadIncreaseHint() {
void CanvasContext::sendLoadIncreaseHint() {
    mHintSessionWrapper.sendLoadIncreaseHint();
    mHintSessionWrapper->sendLoadIncreaseHint();
}
}


void CanvasContext::setSyncDelayDuration(nsecs_t duration) {
void CanvasContext::setSyncDelayDuration(nsecs_t duration) {
@@ -1124,7 +1125,7 @@ void CanvasContext::setSyncDelayDuration(nsecs_t duration) {
}
}


void CanvasContext::startHintSession() {
void CanvasContext::startHintSession() {
    mHintSessionWrapper.init();
    mHintSessionWrapper->init();
}
}


bool CanvasContext::shouldDither() {
bool CanvasContext::shouldDither() {
+1 −1
Original line number Original line Diff line number Diff line
@@ -363,7 +363,7 @@ private:
    std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback;
    std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback;
    std::function<void()> mPrepareSurfaceControlForWebviewCallback;
    std::function<void()> mPrepareSurfaceControlForWebviewCallback;


    HintSessionWrapper mHintSessionWrapper;
    std::shared_ptr<HintSessionWrapper> mHintSessionWrapper;
    nsecs_t mLastDequeueBufferDuration = 0;
    nsecs_t mLastDequeueBufferDuration = 0;
    nsecs_t mSyncDelayDuration = 0;
    nsecs_t mSyncDelayDuration = 0;
    nsecs_t mIdleDuration = 0;
    nsecs_t mIdleDuration = 0;
+31 −6
Original line number Original line Diff line number Diff line
@@ -24,6 +24,7 @@
#include <vector>
#include <vector>


#include "../Properties.h"
#include "../Properties.h"
#include "RenderThread.h"
#include "thread/CommonPool.h"
#include "thread/CommonPool.h"


using namespace std::chrono_literals;
using namespace std::chrono_literals;
@@ -62,8 +63,9 @@ HintSessionWrapper::~HintSessionWrapper() {
}
}


void HintSessionWrapper::destroy() {
void HintSessionWrapper::destroy() {
    if (mHintSessionFuture.valid()) {
    if (mHintSessionFuture.has_value()) {
        mHintSession = mHintSessionFuture.get();
        mHintSession = mHintSessionFuture->get();
        mHintSessionFuture = std::nullopt;
    }
    }
    if (mHintSession) {
    if (mHintSession) {
        mBinding->closeSession(mHintSession);
        mBinding->closeSession(mHintSession);
@@ -74,12 +76,12 @@ void HintSessionWrapper::destroy() {


bool HintSessionWrapper::init() {
bool HintSessionWrapper::init() {
    if (mHintSession != nullptr) return true;
    if (mHintSession != nullptr) return true;

    // If we're waiting for the session
    // If we're waiting for the session
    if (mHintSessionFuture.valid()) {
    if (mHintSessionFuture.has_value()) {
        // If the session is here
        // If the session is here
        if (mHintSessionFuture.wait_for(0s) == std::future_status::ready) {
        if (mHintSessionFuture->wait_for(0s) == std::future_status::ready) {
            mHintSession = mHintSessionFuture.get();
            mHintSession = mHintSessionFuture->get();
            mHintSessionFuture = std::nullopt;
            if (mHintSession != nullptr) {
            if (mHintSession != nullptr) {
                mSessionValid = true;
                mSessionValid = true;
                return true;
                return true;
@@ -136,6 +138,7 @@ void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {
        actualDurationNanos < kSanityCheckUpperBound) {
        actualDurationNanos < kSanityCheckUpperBound) {
        mBinding->reportActualWorkDuration(mHintSession, actualDurationNanos);
        mBinding->reportActualWorkDuration(mHintSession, actualDurationNanos);
    }
    }
    mLastFrameNotification = systemTime();
}
}


void HintSessionWrapper::sendLoadResetHint() {
void HintSessionWrapper::sendLoadResetHint() {
@@ -153,6 +156,28 @@ void HintSessionWrapper::sendLoadResetHint() {
void HintSessionWrapper::sendLoadIncreaseHint() {
void HintSessionWrapper::sendLoadIncreaseHint() {
    if (!init()) return;
    if (!init()) return;
    mBinding->sendHint(mHintSession, static_cast<int32_t>(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 */
} /* namespace renderthread */
+9 −1
Original line number Original line Diff line number Diff line
@@ -19,6 +19,7 @@
#include <android/performance_hint.h>
#include <android/performance_hint.h>


#include <future>
#include <future>
#include <optional>


#include "utils/TimeUtils.h"
#include "utils/TimeUtils.h"


@@ -27,6 +28,8 @@ namespace uirenderer {


namespace renderthread {
namespace renderthread {


class RenderThread;

class HintSessionWrapper {
class HintSessionWrapper {
public:
public:
    friend class HintSessionWrapperTests;
    friend class HintSessionWrapperTests;
@@ -40,10 +43,15 @@ public:
    void sendLoadIncreaseHint();
    void sendLoadIncreaseHint();
    bool init();
    bool init();
    void destroy();
    void destroy();
    bool alive();
    nsecs_t getLastUpdate();
    void delayedDestroy(renderthread::RenderThread& rt, nsecs_t delay,
                        std::shared_ptr<HintSessionWrapper> wrapperPtr);


private:
private:
    APerformanceHintSession* mHintSession = nullptr;
    APerformanceHintSession* mHintSession = nullptr;
    std::future<APerformanceHintSession*> mHintSessionFuture;
    // This needs to work concurrently for testing
    std::optional<std::shared_future<APerformanceHintSession*>> mHintSessionFuture;


    int mResetsSinceLastReport = 0;
    int mResetsSinceLastReport = 0;
    nsecs_t mLastFrameNotification = 0;
    nsecs_t mLastFrameNotification = 0;
+137 −1
Original line number Original line Diff line number Diff line
@@ -23,9 +23,11 @@
#include <chrono>
#include <chrono>


#include "Properties.h"
#include "Properties.h"
#include "tests/common/TestUtils.h"


using namespace testing;
using namespace testing;
using namespace std::chrono_literals;
using namespace std::chrono_literals;
using namespace android::uirenderer::renderthread;


APerformanceHintManager* managerPtr = reinterpret_cast<APerformanceHintManager*>(123);
APerformanceHintManager* managerPtr = reinterpret_cast<APerformanceHintManager*>(123);
APerformanceHintSession* sessionPtr = reinterpret_cast<APerformanceHintSession*>(456);
APerformanceHintSession* sessionPtr = reinterpret_cast<APerformanceHintSession*>(456);
@@ -42,6 +44,9 @@ public:
protected:
protected:
    std::shared_ptr<HintSessionWrapper> mWrapper;
    std::shared_ptr<HintSessionWrapper> mWrapper;


    std::promise<int> blockDestroyCallUntil;
    std::promise<int> waitForDestroyFinished;

    class MockHintSessionBinding : public HintSessionWrapper::HintSessionBinding {
    class MockHintSessionBinding : public HintSessionWrapper::HintSessionBinding {
    public:
    public:
        void init() override;
        void init() override;
@@ -53,11 +58,17 @@ protected:
        MOCK_METHOD(void, fakeUpdateTargetWorkDuration, (APerformanceHintSession*, int64_t));
        MOCK_METHOD(void, fakeUpdateTargetWorkDuration, (APerformanceHintSession*, int64_t));
        MOCK_METHOD(void, fakeReportActualWorkDuration, (APerformanceHintSession*, int64_t));
        MOCK_METHOD(void, fakeReportActualWorkDuration, (APerformanceHintSession*, int64_t));
        MOCK_METHOD(void, fakeSendHint, (APerformanceHintSession*, int32_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
    // Must be static so it can have function pointers we can point to with static methods
    static std::shared_ptr<MockHintSessionBinding> sMockBinding;
    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
    // Must be static so we can point to them as normal fn pointers with HintSessionBinding
    static APerformanceHintManager* stubGetManager() { return sMockBinding->fakeGetManager(); };
    static APerformanceHintManager* stubGetManager() { return sMockBinding->fakeGetManager(); };
    static APerformanceHintSession* stubCreateSession(APerformanceHintManager* manager,
    static APerformanceHintSession* stubCreateSession(APerformanceHintManager* manager,
@@ -65,6 +76,12 @@ protected:
                                                      int64_t initialTarget) {
                                                      int64_t initialTarget) {
        return sMockBinding->fakeCreateSession(manager, ids, idsSize, 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,
    static APerformanceHintSession* stubSlowCreateSession(APerformanceHintManager* manager,
                                                          const int32_t* ids, size_t idsSize,
                                                          const int32_t* ids, size_t idsSize,
                                                          int64_t initialTarget) {
                                                          int64_t initialTarget) {
@@ -85,7 +102,21 @@ protected:
    static void stubSendHint(APerformanceHintSession* session, int32_t hintId) {
    static void stubSendHint(APerformanceHintSession* session, int32_t hintId) {
        sMockBinding->fakeSendHint(session, 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>
std::shared_ptr<HintSessionWrapperTests::MockHintSessionBinding>
@@ -113,6 +144,7 @@ void HintSessionWrapperTests::MockHintSessionBinding::init() {
}
}


void HintSessionWrapperTests::TearDown() {
void HintSessionWrapperTests::TearDown() {
    // Ensure that anything running on RT is completely finished
    mWrapper = nullptr;
    mWrapper = nullptr;
    sMockBinding = nullptr;
    sMockBinding = nullptr;
}
}
@@ -122,6 +154,7 @@ TEST_F(HintSessionWrapperTests, destructorClosesBackgroundSession) {
    sMockBinding->createSession = stubSlowCreateSession;
    sMockBinding->createSession = stubSlowCreateSession;
    mWrapper->init();
    mWrapper->init();
    mWrapper = nullptr;
    mWrapper = nullptr;
    Mock::VerifyAndClearExpectations(sMockBinding.get());
}
}


TEST_F(HintSessionWrapperTests, sessionInitializesCorrectly) {
TEST_F(HintSessionWrapperTests, sessionInitializesCorrectly) {
@@ -148,4 +181,107 @@ TEST_F(HintSessionWrapperTests, loadResetHintsSendCorrectly) {
    mWrapper->sendLoadResetHint();
    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
}  // namespace android::uirenderer::renderthread
 No newline at end of file