diff --git a/services/distributeddataservice/service/cloud/cloud_service_impl.cpp b/services/distributeddataservice/service/cloud/cloud_service_impl.cpp index 3179ab11376288d127fe3f4e7c81c42f98aaa90b..a1f141257df3005726d47cb56ef3601b201cdbe0 100644 --- a/services/distributeddataservice/service/cloud/cloud_service_impl.cpp +++ b/services/distributeddataservice/service/cloud/cloud_service_impl.cpp @@ -805,10 +805,10 @@ int32_t CloudServiceImpl::OnReady(const std::string &device) return NETWORK_ERROR; } for (auto user : users) { - DoKvCloudSync(user, "", MODE_ONLINE); Execute(GenTask(0, user, CloudSyncScene::NETWORK_RECOVERY, - { WORK_CLOUD_INFO_UPDATE, WORK_SCHEMA_UPDATE, WORK_DO_CLOUD_SYNC, WORK_SUB })); + { WORK_CLOUD_INFO_UPDATE, WORK_SCHEMA_UPDATE, WORK_SUB })); } + syncManager_.OnNetworkConnected(); return SUCCESS; } @@ -825,6 +825,7 @@ int32_t CloudServiceImpl::Offline(const std::string &device) } auto it = users.begin(); syncManager_.StopCloudSync(*it); + syncManager_.OnNetworkDisconnected(); return SUCCESS; } diff --git a/services/distributeddataservice/service/cloud/sync_manager.cpp b/services/distributeddataservice/service/cloud/sync_manager.cpp index d35bd45e87626780c474155eaf21e403ca03d144..ea2bc3db7a141858dac4bc1067d88bffd6b75cbe 100644 --- a/services/distributeddataservice/service/cloud/sync_manager.cpp +++ b/services/distributeddataservice/service/cloud/sync_manager.cpp @@ -16,6 +16,7 @@ #include "sync_manager.h" #include +#include #include "account/account_delegate.h" #include "bootstrap.h" @@ -47,6 +48,7 @@ using DmAdapter = OHOS::DistributedData::DeviceManagerAdapter; using Defer = EventCenter::Defer; std::atomic SyncManager::genId_ = 0; constexpr int32_t SYSTEM_USER_ID = 0; +constexpr int32_t NETWORK_DISCONNECT_TIMEOUT_HOURS = 20; static constexpr const char *FT_GET_STORE = "GET_STORE"; static constexpr const char *FT_CALLBACK = "CALLBACK"; SyncManager::SyncInfo::SyncInfo( @@ -364,6 +366,9 @@ ExecutorPool::Task SyncManager::GetSyncTask(int32_t times, bool retry, RefCount UpdateStartSyncInfo(cloudSyncInfos); auto code = IsValid(info, cloud); if (code != E_OK) { + if (code == E_NETWORK_ERROR) { + networkRecoveryManager_.RecordSyncApps(info.user_, info.bundleName_); + } BatchUpdateFinishState(cloudSyncInfos, code); BatchReport(info.user_, traceIds, SyncStage::END, code, "!IsValid"); return; @@ -510,6 +515,9 @@ bool SyncManager::HandleRetryFinished(const SyncInfo &info, int32_t user, int32_ if (code == E_OK || code == E_SYNC_TASK_MERGED) { return true; } + if (code == E_NETWORK_ERROR) { + networkRecoveryManager_.RecordSyncApps(user, info.bundleName_); + } info.SetError(code); RadarReporter::Report({ info.bundleName_.c_str(), CLOUD_SYNC, FINISH_SYNC, info.syncId_, info.triggerMode_, dbCode }, @@ -1081,4 +1089,94 @@ int32_t SyncManager::ConvertValidGeneralCode(int32_t code) { return (code >= E_OK && code < E_BUSY) ? code : E_ERROR; } + +void SyncManager::OnNetworkDisconnected() +{ + networkRecoveryManager_.OnNetworkDisconnected(); +} + +void SyncManager::OnNetworkConnected() +{ + networkRecoveryManager_.OnNetworkConnected(); +} + +void SyncManager::NetworkRecoveryManager::OnNetworkDisconnected() +{ + ZLOGI("network disconnected."); + std::lock_guard lock(eventMutex_); + currentEvent_ = std::make_unique(); + currentEvent_->disconnectTime = std::chrono::system_clock::now(); +} + +void SyncManager::NetworkRecoveryManager::OnNetworkConnected() +{ + std::unique_ptr event; + { + std::lock_guard lock(eventMutex_); + if (currentEvent_ == nullptr) { + ZLOGE("network connected, but currentEvent_ is not initialized."); + return; + } + event = std::move(currentEvent_); + } + auto now = std::chrono::system_clock::now(); + auto duration = now - event->disconnectTime; + auto hours = std::chrono::duration_cast(duration).count(); + bool timeout = (hours > NETWORK_DISCONNECT_TIMEOUT_HOURS); + std::vector users; + if (!Account::GetInstance()->QueryForegroundUsers(users) || users.empty()) { + ZLOGE("no foreground user, skip sync."); + return; + } + for (auto user : users) { + const auto &syncApps = timeout ? GetAppList(user) : event->syncApps[user]; + for (const auto &bundleName : syncApps) { + ZLOGI("sync start bundleName:%{public}s, user:%{public}d", bundleName.c_str(), user); + syncManager_.DoCloudSync(SyncInfo(user, bundleName, "", {}, MODE_ONLINE)); + } + } + ZLOGI("network connected success, network disconnect duration :%{public}ld hours", hours); +} + +void SyncManager::NetworkRecoveryManager::RecordSyncApps(const int32_t user, const std::string &bundleName) +{ + std::lock_guard lock(eventMutex_); + if (currentEvent_ != nullptr) { + auto &syncApps = currentEvent_->syncApps[user]; + if (std::find(syncApps.begin(), syncApps.end(), bundleName) == syncApps.end()) { + syncApps.push_back(bundleName); + ZLOGI("record sync user:%{public}d, bundleName:%{public}s", user, bundleName.c_str()); + } + } +} + +std::vector SyncManager::NetworkRecoveryManager::GetAppList(const int32_t user) +{ + CloudInfo cloud; + cloud.user = user; + if (!MetaDataManager::GetInstance().LoadMeta(cloud.GetKey(), cloud, true)) { + ZLOGE("load cloud info fail, user:%{public}d", user); + return {}; + } + const size_t totalCount = cloud.apps.size(); + std::vector appList; + appList.reserve(totalCount); + std::unordered_set uniqueSet; + uniqueSet.reserve(totalCount); + auto addApp = [&](std::string bundleName) { + if (uniqueSet.insert(bundleName).second) { + appList.push_back(std::move(bundleName)); + } + }; + auto stores = CheckerManager::GetInstance().GetDynamicStores(); + auto staticStores = CheckerManager::GetInstance().GetStaticStores(); + stores.insert(stores.end(), staticStores.begin(), staticStores.end()); + for (const auto &store : stores) { + addApp(std::move(store.bundleName)); + } + for (const auto &[_, app] : cloud.apps) { + addApp(std::move(app.bundleName)); + } + return appList; +} } // namespace OHOS::CloudData \ No newline at end of file diff --git a/services/distributeddataservice/service/cloud/sync_manager.h b/services/distributeddataservice/service/cloud/sync_manager.h index a24f0ae6649a07bb18c8ec6dd8ea8882972cc0be..1e152ca1016457eb312abe14e37f94438aebc057 100644 --- a/services/distributeddataservice/service/cloud/sync_manager.h +++ b/services/distributeddataservice/service/cloud/sync_manager.h @@ -104,8 +104,29 @@ public: void OnScreenUnlocked(int32_t user); void CleanCompensateSync(int32_t userId); static std::string GetPath(const StoreMetaData &meta); + void OnNetworkDisconnected(); + void OnNetworkConnected(); private: + class NetworkRecoveryManager { + public: + explicit NetworkRecoveryManager(SyncManager &syncManager) : syncManager_(syncManager) + { + } + void OnNetworkDisconnected(); + void OnNetworkConnected(); + void RecordSyncApps(const int32_t user, const std::string &bundleName); + + private: + std::vector GetAppList(const int32_t user); + struct NetWorkEvent { + std::chrono::system_clock::time_point disconnectTime; + std::map> syncApps; + }; + std::mutex eventMutex_; + std::unique_ptr currentEvent_; + SyncManager &syncManager_; + }; using Event = DistributedData::Event; using Task = ExecutorPool::Task; using TaskId = ExecutorPool::TaskId; @@ -149,7 +170,7 @@ private: const std::string &message = ""); static void ReportSyncEvent(const DistributedData::SyncEvent &evt, DistributedDataDfx::BizState bizState, int32_t code); - static bool HandleRetryFinished(const SyncInfo &info, int32_t user, int32_t code, int32_t dbCode, + bool HandleRetryFinished(const SyncInfo &info, int32_t user, int32_t code, int32_t dbCode, const std::string &prepareTraceId); Task GetSyncTask(int32_t times, bool retry, RefCount ref, SyncInfo &&syncInfo); void UpdateSchema(const SyncInfo &syncInfo); @@ -185,6 +206,7 @@ private: ConcurrentMap> lastSyncInfos_; std::set kvApps_; ConcurrentMap>> compensateSyncInfos_; + NetworkRecoveryManager networkRecoveryManager_{ *this }; }; } // namespace OHOS::CloudData #endif // OHOS_DISTRIBUTED_DATA_SERVICES_CLOUD_SYNC_MANAGER_H \ No newline at end of file diff --git a/services/distributeddataservice/service/test/BUILD.gn b/services/distributeddataservice/service/test/BUILD.gn index 98c13fef7209ebd20d4b87ac18c56c8738452f45..5d0f0f1720bb73df1e519047e32e453b233ff417 100644 --- a/services/distributeddataservice/service/test/BUILD.gn +++ b/services/distributeddataservice/service/test/BUILD.gn @@ -188,6 +188,7 @@ ohos_unittest("CloudServiceImplTest") { "${data_service_path}/service/cloud/sync_strategies/network_sync_strategy.cpp", "${data_service_path}/service/test/mock/checker_mock.cpp", "cloud_service_impl_test.cpp", + "mock/account_delegate_mock.cpp", ] configs = [ ":module_private_config" ] diff --git a/services/distributeddataservice/service/test/cloud_service_impl_test.cpp b/services/distributeddataservice/service/test/cloud_service_impl_test.cpp index ed40cca5ad9eef4a73600c10d12b4b360a081ec5..6238c5271fd9fe30811acdcaad51a16f24565005 100644 --- a/services/distributeddataservice/service/test/cloud_service_impl_test.cpp +++ b/services/distributeddataservice/service/test/cloud_service_impl_test.cpp @@ -39,6 +39,7 @@ #include "metadata/meta_data_manager.h" #include "metadata/store_meta_data.h" #include "metadata/store_meta_data_local.h" +#include "mock/account_delegate_mock.h" #include "mock/db_store_mock.h" #include "mock/general_store_mock.h" #include "mock/meta_data_manager_mock.h" @@ -69,6 +70,8 @@ static constexpr const char *TEST_CLOUD_BUNDLE = "test_cloud_bundleName"; static constexpr const char *TEST_CLOUD_APPID = "test_cloud_appid"; static constexpr const char *TEST_CLOUD_STORE = "test_cloud_store"; static constexpr const char *TEST_CLOUD_DATABASE_ALIAS_1 = "test_cloud_database_alias_1"; +constexpr const int32_t DISCONNECT_TIME = 21; +constexpr const int32_t MOCK_USER = 200; class CloudServiceImplTest : public testing::Test { public: static void SetUpTestCase(void); @@ -80,9 +83,12 @@ public: static void CheckDelMeta(StoreMetaMapping &metaMapping, StoreMetaData &meta, StoreMetaData &meta1); static std::shared_ptr cloudServiceImpl_; static NetworkDelegateMock delegate_; + static auto ReturnWithUserList(const std::vector& users); + protected: static std::shared_ptr dbStoreMock_; static StoreMetaData metaData_; + static inline AccountDelegateMock *accountDelegateMock = nullptr; }; std::shared_ptr CloudServiceImplTest::cloudServiceImpl_ = std::make_shared(); @@ -127,12 +133,22 @@ void CloudServiceImplTest::InitMetaData() metaData_.dataDir = "/test_cloud_service_impl_store"; } +auto CloudServiceImplTest::ReturnWithUserList(const std::vector &users) +{ + return Invoke([=](std::vector &outUsers) -> bool { + outUsers = users; + return true; + }); +} + void CloudServiceImplTest::SetUpTestCase(void) { size_t max = 12; size_t min = 5; auto executor = std::make_shared(max, min); DeviceManagerAdapter::GetInstance().Init(executor); + cloudServiceImpl_->OnBind( + { "CloudServiceImplTest", static_cast(IPCSkeleton::GetSelfTokenID()), std::move(executor) }); Bootstrap::GetInstance().LoadCheckers(); CryptoManager::GetInstance().GenerateRootKey(); MetaDataManager::GetInstance().Initialize(dbStoreMock_, nullptr, ""); @@ -961,5 +977,181 @@ HWTEST_F(ComponentConfigTest, ComponentConfig, TestSize.Level0) EXPECT_EQ(node["constructor"], componentConfig.constructor); EXPECT_EQ(node["destructor"], componentConfig.destructor); } + +/** + * @tc.name: NetworkRecoveryTest001 + * @tc.desc: test the compensatory sync strategy for network disconnection times of less than 20 hours + * @tc.type: FUNC + * @tc.require: + * @tc.author: + */ +HWTEST_F(CloudServiceImplTest, NetworkRecoveryTest001, TestSize.Level0) +{ + ASSERT_NE(cloudServiceImpl_, nullptr); + accountDelegateMock = new (std::nothrow) AccountDelegateMock(); + ASSERT_NE(accountDelegateMock, nullptr); + AccountDelegate::instance_ = nullptr; + AccountDelegate::RegisterAccountInstance(accountDelegateMock); + + EXPECT_CALL(*accountDelegateMock, IsLoginAccount()).WillOnce(Return(true)); + EXPECT_CALL(*accountDelegateMock, IsVerified(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*accountDelegateMock, QueryUsers(_)).WillOnce(ReturnWithUserList({ MOCK_USER })); + // 2 means that the QueryForegroundUsers interface will be called twice + EXPECT_CALL(*accountDelegateMock, QueryForegroundUsers(_)) + .Times(2) + .WillRepeatedly(ReturnWithUserList({ MOCK_USER })); + EXPECT_CALL(*accountDelegateMock, GetUserByToken(_)).WillOnce(Return(MOCK_USER)); + delegate_.isNetworkAvailable_ = false; + CloudInfo::AppInfo appInfo; + appInfo.bundleName = TEST_CLOUD_BUNDLE; + appInfo.cloudSwitch = true; + std::map apps; + apps.emplace(TEST_CLOUD_BUNDLE, appInfo); + CloudInfo cloudInfo; + cloudInfo.apps = apps; + cloudInfo.user = MOCK_USER; + cloudInfo.enableCloud = true; + MetaDataManager::GetInstance().SaveMeta(cloudInfo.GetKey(), cloudInfo, true); + auto &recoveryManager = cloudServiceImpl_->syncManager_.networkRecoveryManager_; + cloudServiceImpl_->Offline(DeviceManagerAdapter::CLOUD_DEVICE_UUID); + ASSERT_NE(recoveryManager.currentEvent_, nullptr); + + SchemaMeta schemaMeta; + schemaMeta.bundleName = TEST_CLOUD_BUNDLE; + SchemaMeta::Database database; + database.name = TEST_CLOUD_STORE; + schemaMeta.databases.emplace_back(database); + MetaDataManager::GetInstance().SaveMeta(CloudInfo::GetSchemaKey(cloudInfo.user, TEST_CLOUD_BUNDLE), schemaMeta, + true); + CloudData::CloudService::Option option; + option.syncMode = DistributedData::GeneralStore::CLOUD_BEGIN; + auto async = [](const DistributedRdb::Details &details) {}; + cloudServiceImpl_->CloudSync(TEST_CLOUD_BUNDLE, TEST_CLOUD_STORE, option, async); + EXPECT_CALL(*accountDelegateMock, GetUserByToken(_)).WillOnce(Return(MOCK_USER)); + cloudServiceImpl_->CloudSync(TEST_CLOUD_BUNDLE, TEST_CLOUD_STORE, option, async); + sleep(1); + EXPECT_EQ(recoveryManager.currentEvent_->syncApps.size(), 1); + delegate_.isNetworkAvailable_ = true; + cloudServiceImpl_->OnReady(DeviceManagerAdapter::CLOUD_DEVICE_UUID); + EXPECT_EQ(recoveryManager.currentEvent_, nullptr); +} + +/** + * @tc.name: NetworkRecoveryTest002 + * @tc.desc: test the compensatory sync strategy for network disconnection times of more than 20 hours + * @tc.type: FUNC + * @tc.require: + * @tc.author: + */ +HWTEST_F(CloudServiceImplTest, NetworkRecoveryTest002, TestSize.Level0) +{ + ASSERT_NE(cloudServiceImpl_, nullptr); + EXPECT_CALL(*accountDelegateMock, IsVerified(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*accountDelegateMock, IsLoginAccount()).WillOnce(Return(true)); + EXPECT_CALL(*accountDelegateMock, QueryUsers(_)).WillOnce(Invoke([&](std::vector &users) -> bool { + users = { MOCK_USER }; + return true; + })); + // 2 means that the QueryForegroundUsers interface will be called twice + EXPECT_CALL(*accountDelegateMock, QueryForegroundUsers(_)) + .Times(2) + .WillRepeatedly(ReturnWithUserList({ MOCK_USER })); + auto &recoveryManager = cloudServiceImpl_->syncManager_.networkRecoveryManager_; + cloudServiceImpl_->Offline(DeviceManagerAdapter::CLOUD_DEVICE_UUID); + ASSERT_NE(recoveryManager.currentEvent_, nullptr); + recoveryManager.currentEvent_->disconnectTime -= std::chrono::hours(DISCONNECT_TIME); + cloudServiceImpl_->OnReady(DeviceManagerAdapter::CLOUD_DEVICE_UUID); + EXPECT_EQ(recoveryManager.currentEvent_, nullptr); +} + +/** + * @tc.name: NetworkRecoveryTest003 + * @tc.desc: The test only calls the network connection interface but not disconnect + * @tc.type: FUNC + * @tc.require: + * @tc.author: + */ +HWTEST_F(CloudServiceImplTest, NetworkRecoveryTest003, TestSize.Level0) +{ + ASSERT_NE(cloudServiceImpl_, nullptr); + EXPECT_CALL(*accountDelegateMock, IsVerified(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*accountDelegateMock, IsLoginAccount()).WillOnce(Return(true)); + EXPECT_CALL(*accountDelegateMock, QueryForegroundUsers(_)).WillOnce(ReturnWithUserList({ MOCK_USER })); + auto &recoveryManager = cloudServiceImpl_->syncManager_.networkRecoveryManager_; + CloudData::CloudService::Option option; + option.syncMode = DistributedData::GeneralStore::CLOUD_BEGIN; + auto async = [](const DistributedRdb::Details &details) {}; + EXPECT_CALL(*accountDelegateMock, GetUserByToken(_)).WillOnce(Return(MOCK_USER)); + cloudServiceImpl_->CloudSync(TEST_CLOUD_BUNDLE, TEST_CLOUD_STORE, option, async); + sleep(1); + cloudServiceImpl_->OnReady(DeviceManagerAdapter::CLOUD_DEVICE_UUID); + EXPECT_EQ(recoveryManager.currentEvent_, nullptr); +} + +/** + * @tc.name: NetworkRecoveryTest004 + * @tc.desc: The QueryForegroundUsers interface call fails when the network is restored + * @tc.type: FUNC + * @tc.require: + * @tc.author: + */ +HWTEST_F(CloudServiceImplTest, NetworkRecoveryTest004, TestSize.Level0) +{ + ASSERT_NE(cloudServiceImpl_, nullptr); + EXPECT_CALL(*accountDelegateMock, IsVerified(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*accountDelegateMock, QueryUsers(_)).WillOnce(ReturnWithUserList({ MOCK_USER })); + cloudServiceImpl_->Offline(DeviceManagerAdapter::CLOUD_DEVICE_UUID); + auto &recoveryManager = cloudServiceImpl_->syncManager_.networkRecoveryManager_; + ASSERT_NE(recoveryManager.currentEvent_, nullptr); + + EXPECT_CALL(*accountDelegateMock, IsLoginAccount()).WillOnce(Return(true)); + EXPECT_CALL(*accountDelegateMock, QueryForegroundUsers(_)) + .WillOnce(ReturnWithUserList({ MOCK_USER })) + .WillOnce(Return(false)); + cloudServiceImpl_->OnReady(DeviceManagerAdapter::CLOUD_DEVICE_UUID); + EXPECT_EQ(recoveryManager.currentEvent_, nullptr); + + EXPECT_CALL(*accountDelegateMock, QueryUsers(_)).WillOnce(ReturnWithUserList({ MOCK_USER })); + cloudServiceImpl_->Offline(DeviceManagerAdapter::CLOUD_DEVICE_UUID); + ASSERT_NE(recoveryManager.currentEvent_, nullptr); + EXPECT_CALL(*accountDelegateMock, IsLoginAccount()).WillOnce(Return(true)); + EXPECT_CALL(*accountDelegateMock, QueryForegroundUsers(_)) + .WillOnce(ReturnWithUserList({ MOCK_USER })) + .WillOnce(ReturnWithUserList({})); + cloudServiceImpl_->OnReady(DeviceManagerAdapter::CLOUD_DEVICE_UUID); + EXPECT_EQ(recoveryManager.currentEvent_, nullptr); +} + +/** + * @tc.name: NetworkRecoveryTest005 + * @tc.desc: The test network connection interface call fails when the load cloudInfo failed + * @tc.type: FUNC + * @tc.require: + * @tc.author: + */ +HWTEST_F(CloudServiceImplTest, NetworkRecoveryTest005, TestSize.Level0) +{ + ASSERT_NE(cloudServiceImpl_, nullptr); + EXPECT_CALL(*accountDelegateMock, IsVerified(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*accountDelegateMock, IsLoginAccount()).WillOnce(Return(true)); + EXPECT_CALL(*accountDelegateMock, QueryUsers(_)).WillOnce(ReturnWithUserList({ MOCK_USER })); + // 2 means that the QueryForegroundUsers interface will be called twice + EXPECT_CALL(*accountDelegateMock, QueryForegroundUsers(_)) + .Times(2) + .WillRepeatedly(ReturnWithUserList({ MOCK_USER })); + CloudInfo cloudInfo; + cloudInfo.user = MOCK_USER; + MetaDataManager::GetInstance().DelMeta(cloudInfo.GetKey(), true); + auto &recoveryManager = cloudServiceImpl_->syncManager_.networkRecoveryManager_; + cloudServiceImpl_->Offline(DeviceManagerAdapter::CLOUD_DEVICE_UUID); + ASSERT_NE(recoveryManager.currentEvent_, nullptr); + recoveryManager.currentEvent_->disconnectTime -= std::chrono::hours(DISCONNECT_TIME); + cloudServiceImpl_->OnReady(DeviceManagerAdapter::CLOUD_DEVICE_UUID); + EXPECT_EQ(recoveryManager.currentEvent_, nullptr); + if (accountDelegateMock != nullptr) { + delete accountDelegateMock; + accountDelegateMock = nullptr; + } +} } // namespace DistributedDataTest } // namespace OHOS::Test \ No newline at end of file