Lifecycle: detecting pending reads.
Once pending read detected, try to start the dataloader.
Bug: 153874006
Test: test PackageManagerShellCommandTest PackageManagerShellCommandIncrementalTest IncrementalServiceTest
Change-Id: Ia8169ccbb0f710317715e6fddb9bc6a718543766
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index c3c2157..d412a19 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -266,6 +266,7 @@
mIncFs(sm.getIncFs()),
mAppOpsManager(sm.getAppOpsManager()),
mJni(sm.getJni()),
+ mLooper(sm.getLooper()),
mIncrementalDir(rootDir) {
if (!mVold) {
LOG(FATAL) << "Vold service is unavailable";
@@ -276,12 +277,22 @@
if (!mAppOpsManager) {
LOG(FATAL) << "AppOpsManager is unavailable";
}
+ if (!mJni) {
+ LOG(FATAL) << "JNI is unavailable";
+ }
+ if (!mLooper) {
+ LOG(FATAL) << "Looper is unavailable";
+ }
mJobQueue.reserve(16);
mJobProcessor = std::thread([this]() {
mJni->initializeForCurrentThread();
runJobProcessing();
});
+ mCmdLooperThread = std::thread([this]() {
+ mJni->initializeForCurrentThread();
+ runCmdLooper();
+ });
const auto mountedRootNames = adoptMountedInstances();
mountExistingImages(mountedRootNames);
@@ -294,6 +305,7 @@
}
mJobCondition.notify_all();
mJobProcessor.join();
+ mCmdLooperThread.join();
}
static const char* toString(IncrementalService::BindKind kind) {
@@ -1315,6 +1327,13 @@
return true;
}
+void IncrementalService::runCmdLooper() {
+ constexpr auto kTimeoutMsecs = 1000;
+ while (mRunning.load(std::memory_order_relaxed)) {
+ mLooper->pollAll(kTimeoutMsecs);
+ }
+}
+
IncrementalService::DataLoaderStubPtr IncrementalService::prepareDataLoader(
IncFsMount& ifs, DataLoaderParamsParcel&& params,
const DataLoaderStatusListener* externalListener) {
@@ -1337,8 +1356,16 @@
fsControlParcel.incremental->log.reset(dup(ifs.control.logs()));
fsControlParcel.service = new IncrementalServiceConnector(*this, ifs.mountId);
- ifs.dataLoaderStub = new DataLoaderStub(*this, ifs.mountId, std::move(params),
- std::move(fsControlParcel), externalListener);
+ incfs::UniqueControl healthControl = mIncFs->openMount(ifs.root.c_str());
+ if (healthControl.pendingReads() < 0) {
+ LOG(ERROR) << "Failed to open health control for: " << ifs.root << "("
+ << healthControl.cmd() << ":" << healthControl.pendingReads() << ":"
+ << healthControl.logs() << ")";
+ }
+
+ ifs.dataLoaderStub =
+ new DataLoaderStub(*this, ifs.mountId, std::move(params), std::move(fsControlParcel),
+ std::move(healthControl), externalListener);
}
template <class Duration>
@@ -1658,24 +1685,34 @@
IncrementalService::DataLoaderStub::DataLoaderStub(IncrementalService& service, MountId id,
DataLoaderParamsParcel&& params,
FileSystemControlParcel&& control,
+ incfs::UniqueControl&& healthControl,
const DataLoaderStatusListener* externalListener)
: mService(service),
mId(id),
mParams(std::move(params)),
mControl(std::move(control)),
+ mHealthControl(std::move(healthControl)),
mListener(externalListener ? *externalListener : DataLoaderStatusListener()) {
+ addToCmdLooperLocked();
}
-IncrementalService::DataLoaderStub::~DataLoaderStub() = default;
+IncrementalService::DataLoaderStub::~DataLoaderStub() {
+ if (mId != kInvalidStorageId) {
+ cleanupResources();
+ }
+}
void IncrementalService::DataLoaderStub::cleanupResources() {
requestDestroy();
auto now = Clock::now();
-
std::unique_lock lock(mMutex);
+
+ removeFromCmdLooperLocked();
+
mParams = {};
mControl = {};
+ mHealthControl = {};
mStatusCondition.wait_until(lock, now + 60s, [this] {
return mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
});
@@ -1710,21 +1747,19 @@
}
bool IncrementalService::DataLoaderStub::setTargetStatus(int newStatus) {
- int oldStatus, curStatus;
{
std::unique_lock lock(mMutex);
- oldStatus = mTargetStatus;
- curStatus = mCurrentStatus;
setTargetStatusLocked(newStatus);
}
- LOG(DEBUG) << "Target status update for DataLoader " << mId << ": " << oldStatus << " -> "
- << newStatus << " (current " << curStatus << ")";
return fsmStep();
}
void IncrementalService::DataLoaderStub::setTargetStatusLocked(int status) {
+ auto oldStatus = mTargetStatus;
mTargetStatus = status;
mTargetStatusTs = Clock::now();
+ LOG(DEBUG) << "Target status update for DataLoader " << mId << ": " << oldStatus << " -> "
+ << status << " (current " << mCurrentStatus << ")";
}
bool IncrementalService::DataLoaderStub::bind() {
@@ -1860,12 +1895,75 @@
return binder::Status::ok();
}
+void IncrementalService::DataLoaderStub::addToCmdLooperLocked() {
+ const auto pendingReadsFd = mHealthControl.pendingReads();
+ if (pendingReadsFd < 0) {
+ return;
+ }
+
+ mService.mLooper->addFd(
+ pendingReadsFd, android::Looper::POLL_CALLBACK, android::Looper::EVENT_INPUT,
+ [](int, int, void* data) -> int {
+ auto&& self = (DataLoaderStub*)data;
+ return self->onCmdLooperEvent();
+ },
+ this);
+ mService.mLooper->wake();
+}
+
+void IncrementalService::DataLoaderStub::removeFromCmdLooperLocked() {
+ const auto pendingReadsFd = mHealthControl.pendingReads();
+ if (pendingReadsFd < 0) {
+ return;
+ }
+
+ mService.mLooper->removeFd(pendingReadsFd);
+ mService.mLooper->wake();
+}
+
+int IncrementalService::DataLoaderStub::onCmdLooperEvent() {
+ if (!mService.mRunning.load(std::memory_order_relaxed)) {
+ return 0;
+ }
+
+ bool pendingReadsOccur = false;
+
+ {
+ std::unique_lock lock(mMutex);
+ const auto now = Clock::now();
+ if (now < mEarliestMissingPageTs) {
+ // Transition: duration::max -> now.
+ mEarliestMissingPageTs = now;
+ pendingReadsOccur = true;
+ }
+ }
+
+ if (pendingReadsOccur) {
+ LOG(INFO) << "Pending reads occur for, requesting start for: " << mId;
+ requestStart();
+ }
+
+ {
+ // Drop pending reads.
+ static constexpr auto kMaxDropIterations = 3;
+ std::unique_lock lock(mMutex);
+ for (int i = 0; i < kMaxDropIterations; ++i) {
+ if (IncFs_DropPendingReads(mHealthControl) <= 0) {
+ break;
+ }
+ }
+ }
+ return 1;
+}
+
void IncrementalService::DataLoaderStub::onDump(int fd) {
dprintf(fd, " dataLoader: {\n");
dprintf(fd, " currentStatus: %d\n", mCurrentStatus);
dprintf(fd, " targetStatus: %d\n", mTargetStatus);
dprintf(fd, " targetStatusTs: %lldmcs\n",
(long long)(elapsedMcs(mTargetStatusTs, Clock::now())));
+ dprintf(fd, " earliestMissingPageTs: %lldmcs\n",
+ (long long)(elapsedMcs(mEarliestMissingPageTs, Clock::now())));
const auto& params = mParams;
dprintf(fd, " dataLoaderParams: {\n");
dprintf(fd, " type: %s\n", toString(params.type).c_str());
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index cf310b1..640ca53 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -18,6 +18,7 @@
#include <android/content/pm/BnDataLoaderStatusListener.h>
#include <android/content/pm/DataLoaderParamsParcel.h>
+#include <android/content/pm/FileSystemControlParcel.h>
#include <android/content/pm/IDataLoaderStatusListener.h>
#include <android/os/incremental/BnIncrementalServiceConnector.h>
#include <binder/IAppOpsCallback.h>
@@ -160,6 +161,7 @@
DataLoaderStub(IncrementalService& service, MountId id,
content::pm::DataLoaderParamsParcel&& params,
content::pm::FileSystemControlParcel&& control,
+ incfs::UniqueControl&& healthControl,
const DataLoaderStatusListener* externalListener);
~DataLoaderStub();
// Cleans up the internal state and invalidates DataLoaderStub. Any subsequent calls will
@@ -178,6 +180,10 @@
private:
binder::Status onStatusChanged(MountId mount, int newStatus) final;
+ void addToCmdLooperLocked();
+ void removeFromCmdLooperLocked();
+ int onCmdLooperEvent();
+
bool isValid() const { return mId != kInvalidStorageId; }
sp<content::pm::IDataLoader> getDataLoader();
@@ -197,12 +203,15 @@
MountId mId = kInvalidStorageId;
content::pm::DataLoaderParamsParcel mParams;
content::pm::FileSystemControlParcel mControl;
+ incfs::UniqueControl mHealthControl;
DataLoaderStatusListener mListener;
std::condition_variable mStatusCondition;
int mCurrentStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
int mTargetStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
TimePoint mTargetStatusTs = {};
+
+ TimePoint mEarliestMissingPageTs{Clock::duration::max()};
};
using DataLoaderStubPtr = sp<DataLoaderStub>;
@@ -300,12 +309,15 @@
const incfs::FileId& libFileId, std::string_view targetLibPath,
Clock::time_point scheduledTs);
+ void runCmdLooper();
+
private:
const std::unique_ptr<VoldServiceWrapper> mVold;
const std::unique_ptr<DataLoaderManagerWrapper> mDataLoaderManager;
const std::unique_ptr<IncFsWrapper> mIncFs;
const std::unique_ptr<AppOpsManagerWrapper> mAppOpsManager;
const std::unique_ptr<JniWrapper> mJni;
+ const std::unique_ptr<LooperWrapper> mLooper;
const std::string mIncrementalDir;
mutable std::mutex mLock;
@@ -319,13 +331,16 @@
std::atomic_bool mSystemReady = false;
StorageId mNextId = 0;
+ std::atomic_bool mRunning{true};
+
using Job = std::function<void()>;
std::unordered_map<MountId, std::vector<Job>> mJobQueue;
MountId mPendingJobsMount = kInvalidStorageId;
std::condition_variable mJobCondition;
std::mutex mJobMutex;
std::thread mJobProcessor;
- bool mRunning = true;
+
+ std::thread mCmdLooperThread;
};
} // namespace android::incremental
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 85f7441..08fb486 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -113,6 +113,23 @@
JavaVM* const mJvm;
};
+class RealLooperWrapper final : public LooperWrapper {
+public:
+ int addFd(int fd, int ident, int events, android::Looper_callbackFunc callback,
+ void* data) final {
+ return mLooper.addFd(fd, ident, events, callback, data);
+ }
+ int removeFd(int fd) final { return mLooper.removeFd(fd); }
+ void wake() final { return mLooper.wake(); }
+ int pollAll(int timeoutMillis) final { return mLooper.pollAll(timeoutMillis); }
+
+private:
+ struct Looper : public android::Looper {
+ Looper() : android::Looper(/*allowNonCallbacks=*/false) {}
+ ~Looper() {}
+ } mLooper;
+};
+
class RealIncFs : public IncFsWrapper {
public:
RealIncFs() = default;
@@ -203,6 +220,10 @@
return std::make_unique<RealJniWrapper>(mJvm);
}
+std::unique_ptr<LooperWrapper> RealServiceManager::getLooper() {
+ return std::make_unique<RealLooperWrapper>();
+}
+
static JavaVM* getJavaVm(JNIEnv* env) {
CHECK(env);
JavaVM* jvm = nullptr;
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 3792830..abbf2f4 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -26,6 +26,7 @@
#include <binder/Status.h>
#include <incfs.h>
#include <jni.h>
+#include <utils/Looper.h>
#include <memory>
#include <span>
@@ -106,6 +107,16 @@
virtual void initializeForCurrentThread() const = 0;
};
+class LooperWrapper {
+public:
+ virtual ~LooperWrapper() = default;
+ virtual int addFd(int fd, int ident, int events, android::Looper_callbackFunc callback,
+ void* data) = 0;
+ virtual int removeFd(int fd) = 0;
+ virtual void wake() = 0;
+ virtual int pollAll(int timeoutMillis) = 0;
+};
+
class ServiceManagerWrapper {
public:
virtual ~ServiceManagerWrapper() = default;
@@ -114,6 +125,7 @@
virtual std::unique_ptr<IncFsWrapper> getIncFs() = 0;
virtual std::unique_ptr<AppOpsManagerWrapper> getAppOpsManager() = 0;
virtual std::unique_ptr<JniWrapper> getJni() = 0;
+ virtual std::unique_ptr<LooperWrapper> getLooper() = 0;
};
// --- Real stuff ---
@@ -127,6 +139,7 @@
std::unique_ptr<IncFsWrapper> getIncFs() final;
std::unique_ptr<AppOpsManagerWrapper> getAppOpsManager() final;
std::unique_ptr<JniWrapper> getJni() final;
+ std::unique_ptr<LooperWrapper> getLooper() final;
private:
template <class INTERFACE>
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 2205bfe..325218d 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -242,6 +242,9 @@
void setDataLoaderStatusDestroyed() {
mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
}
+ void setDataLoaderStatusUnavailable() {
+ mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE);
+ }
binder::Status unbindFromDataLoaderOk(int32_t id) {
if (mDataLoader) {
if (auto status = mDataLoader->destroy(id); !status.isOk()) {
@@ -286,6 +289,14 @@
void makeFileFails() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(-1)); }
void makeFileSuccess() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(0)); }
+ void openMountSuccess() {
+ ON_CALL(*this, openMount(_)).WillByDefault(Invoke(this, &MockIncFs::openMountForHealth));
+ }
+
+ static constexpr auto kPendingReadsFd = 42;
+ Control openMountForHealth(std::string_view) {
+ return UniqueControl(IncFs_CreateControl(-1, kPendingReadsFd, -1));
+ }
RawMetadata getMountInfoMetadata(const Control& control, std::string_view path) {
metadata::Mount m;
@@ -346,7 +357,42 @@
public:
MOCK_CONST_METHOD0(initializeForCurrentThread, void());
- MockJniWrapper() { EXPECT_CALL(*this, initializeForCurrentThread()).Times(1); }
+ MockJniWrapper() { EXPECT_CALL(*this, initializeForCurrentThread()).Times(2); }
+};
+
+class MockLooperWrapper : public LooperWrapper {
+public:
+ MOCK_METHOD5(addFd, int(int, int, int, android::Looper_callbackFunc, void*));
+ MOCK_METHOD1(removeFd, int(int));
+ MOCK_METHOD0(wake, void());
+ MOCK_METHOD1(pollAll, int(int));
+
+ MockLooperWrapper() {
+ ON_CALL(*this, addFd(_, _, _, _, _))
+ .WillByDefault(Invoke(this, &MockLooperWrapper::storeCallback));
+ ON_CALL(*this, removeFd(_)).WillByDefault(Invoke(this, &MockLooperWrapper::clearCallback));
+ ON_CALL(*this, pollAll(_)).WillByDefault(Invoke(this, &MockLooperWrapper::sleepFor));
+ }
+
+ int storeCallback(int, int, int, android::Looper_callbackFunc callback, void* data) {
+ mCallback = callback;
+ mCallbackData = data;
+ return 0;
+ }
+
+ int clearCallback(int) {
+ mCallback = nullptr;
+ mCallbackData = nullptr;
+ return 0;
+ }
+
+ int sleepFor(int timeoutMillis) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(timeoutMillis));
+ return 0;
+ }
+
+ android::Looper_callbackFunc mCallback = nullptr;
+ void* mCallbackData = nullptr;
};
class MockServiceManager : public ServiceManagerWrapper {
@@ -355,12 +401,14 @@
std::unique_ptr<MockDataLoaderManager> dataLoaderManager,
std::unique_ptr<MockIncFs> incfs,
std::unique_ptr<MockAppOpsManager> appOpsManager,
- std::unique_ptr<MockJniWrapper> jni)
+ std::unique_ptr<MockJniWrapper> jni,
+ std::unique_ptr<MockLooperWrapper> looper)
: mVold(std::move(vold)),
mDataLoaderManager(std::move(dataLoaderManager)),
mIncFs(std::move(incfs)),
mAppOpsManager(std::move(appOpsManager)),
- mJni(std::move(jni)) {}
+ mJni(std::move(jni)),
+ mLooper(std::move(looper)) {}
std::unique_ptr<VoldServiceWrapper> getVoldService() final { return std::move(mVold); }
std::unique_ptr<DataLoaderManagerWrapper> getDataLoaderManager() final {
return std::move(mDataLoaderManager);
@@ -368,6 +416,7 @@
std::unique_ptr<IncFsWrapper> getIncFs() final { return std::move(mIncFs); }
std::unique_ptr<AppOpsManagerWrapper> getAppOpsManager() final { return std::move(mAppOpsManager); }
std::unique_ptr<JniWrapper> getJni() final { return std::move(mJni); }
+ std::unique_ptr<LooperWrapper> getLooper() final { return std::move(mLooper); }
private:
std::unique_ptr<MockVoldService> mVold;
@@ -375,6 +424,7 @@
std::unique_ptr<MockIncFs> mIncFs;
std::unique_ptr<MockAppOpsManager> mAppOpsManager;
std::unique_ptr<MockJniWrapper> mJni;
+ std::unique_ptr<MockLooperWrapper> mLooper;
};
// --- IncrementalServiceTest ---
@@ -394,13 +444,16 @@
mAppOpsManager = appOps.get();
auto jni = std::make_unique<NiceMock<MockJniWrapper>>();
mJni = jni.get();
+ auto looper = std::make_unique<NiceMock<MockLooperWrapper>>();
+ mLooper = looper.get();
mIncrementalService =
std::make_unique<IncrementalService>(MockServiceManager(std::move(vold),
std::move(
dataloaderManager),
std::move(incFs),
std::move(appOps),
- std::move(jni)),
+ std::move(jni),
+ std::move(looper)),
mRootDir.path);
mDataLoaderParcel.packageName = "com.test";
mDataLoaderParcel.arguments = "uri";
@@ -430,12 +483,13 @@
}
protected:
- NiceMock<MockVoldService>* mVold;
- NiceMock<MockIncFs>* mIncFs;
- NiceMock<MockDataLoaderManager>* mDataLoaderManager;
- NiceMock<MockAppOpsManager>* mAppOpsManager;
- NiceMock<MockJniWrapper>* mJni;
- NiceMock<MockDataLoader>* mDataLoader;
+ NiceMock<MockVoldService>* mVold = nullptr;
+ NiceMock<MockIncFs>* mIncFs = nullptr;
+ NiceMock<MockDataLoaderManager>* mDataLoaderManager = nullptr;
+ NiceMock<MockAppOpsManager>* mAppOpsManager = nullptr;
+ NiceMock<MockJniWrapper>* mJni = nullptr;
+ NiceMock<MockLooperWrapper>* mLooper = nullptr;
+ NiceMock<MockDataLoader>* mDataLoader = nullptr;
std::unique_ptr<IncrementalService> mIncrementalService;
TemporaryDir mRootDir;
DataLoaderParamsParcel mDataLoaderParcel;
@@ -593,6 +647,54 @@
mDataLoaderManager->setDataLoaderStatusCreated();
}
+TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnavailable) {
+ mVold->mountIncFsSuccess();
+ mIncFs->makeFileSuccess();
+ mVold->bindMountSuccess();
+ mDataLoader->initializeCreateOkNoStatus();
+ mDataLoaderManager->bindToDataLoaderSuccess();
+ mDataLoaderManager->getDataLoaderSuccess();
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(0);
+ EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+ mDataLoaderManager->setDataLoaderStatusUnavailable();
+}
+
+TEST_F(IncrementalServiceTest, testStartDataLoaderRecreateOnPendingReads) {
+ mVold->mountIncFsSuccess();
+ mIncFs->makeFileSuccess();
+ mIncFs->openMountSuccess();
+ mVold->bindMountSuccess();
+ mDataLoader->initializeCreateOkNoStatus();
+ mDataLoaderManager->bindToDataLoaderSuccess();
+ mDataLoaderManager->getDataLoaderSuccess();
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2);
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(0);
+ EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ EXPECT_CALL(*mLooper, addFd(MockIncFs::kPendingReadsFd, _, _, _, _)).Times(1);
+ EXPECT_CALL(*mLooper, removeFd(MockIncFs::kPendingReadsFd)).Times(1);
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+ mDataLoaderManager->setDataLoaderStatusUnavailable();
+ ASSERT_NE(nullptr, mLooper->mCallback);
+ ASSERT_NE(nullptr, mLooper->mCallbackData);
+ mLooper->mCallback(-1, -1, mLooper->mCallbackData);
+}
+
TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccess) {
mVold->mountIncFsSuccess();
mIncFs->makeFileSuccess();