Skip to content

Commit 19d40bb

Browse files
committed
Only perform launch actions if the metadata store initiated the session
1 parent a592484 commit 19d40bb

File tree

6 files changed

+102
-15
lines changed

6 files changed

+102
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
### Enhancements
44
* <New feature description> (PR [#????](https://github.com/realm/realm-core/pull/????))
5-
* None.
5+
* "Next launch" metadata file actions are now performed in a multi-process safe manner ([#7576](https://github.com/realm/realm-core/pull/7576)).
66

77
### Fixed
88
* <How do the end-user experience this issue? what was the impact?> ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?)

src/realm/db.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2860,20 +2860,27 @@ DBRef DB::create(BinaryData buffer, bool take_ownership) NO_THREAD_SAFETY_ANALYS
28602860
return retval;
28612861
}
28622862

2863-
void DB::claim_sync_agent()
2863+
bool DB::try_claim_sync_agent()
28642864
{
28652865
REALM_ASSERT(is_attached());
2866-
std::unique_lock<InterprocessMutex> lock(m_controlmutex);
2866+
std::lock_guard lock(m_controlmutex);
28672867
if (m_info->sync_agent_present)
2868-
throw MultipleSyncAgents{};
2868+
return false;
28692869
m_info->sync_agent_present = 1; // Set to true
28702870
m_is_sync_agent = true;
2871+
return true;
2872+
}
2873+
2874+
void DB::claim_sync_agent()
2875+
{
2876+
if (!try_claim_sync_agent())
2877+
throw MultipleSyncAgents{};
28712878
}
28722879

28732880
void DB::release_sync_agent()
28742881
{
28752882
REALM_ASSERT(is_attached());
2876-
std::unique_lock<InterprocessMutex> lock(m_controlmutex);
2883+
std::lock_guard lock(m_controlmutex);
28772884
if (!m_is_sync_agent)
28782885
return;
28792886
REALM_ASSERT(m_info->sync_agent_present);

src/realm/db.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ class DB : public std::enable_shared_from_this<DB> {
432432
/// Mark this DB as the sync agent for the file.
433433
/// \throw MultipleSyncAgents if another DB is already the sync agent.
434434
void claim_sync_agent();
435+
bool try_claim_sync_agent();
435436
void release_sync_agent();
436437

437438
/// Returns true if there are threads waiting to acquire the write lock, false otherwise.

src/realm/object-store/impl/realm_coordinator.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,11 @@ class RealmCoordinator : public std::enable_shared_from_this<RealmCoordinator>,
228228
return m_audit_context.get();
229229
}
230230

231+
bool try_claim_sync_agent()
232+
{
233+
return m_db->try_claim_sync_agent();
234+
}
235+
231236
private:
232237
friend Realm::Internal;
233238
Realm::Config m_config;

src/realm/object-store/sync/impl/app_metadata.cpp

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -297,10 +297,6 @@ std::shared_ptr<Realm> open_realm(RealmConfig& config, const app::AppConfig& app
297297
}
298298
};
299299

300-
// This logic is all a giant race condition once we have multi-process sync.
301-
// Wrapping it all (including the keychain accesses) in DB::call_with_lock()
302-
// might suffice.
303-
304300
// First try to open the Realm with a key already stored in the keychain.
305301
// This works for both the case where everything is sensible and valid and
306302
// when we have a key but no metadata Realm.
@@ -381,12 +377,13 @@ struct PersistedSyncMetadataManager : public app::MetadataStore {
381377
m_user_identity_schema.read(*realm);
382378
m_current_user_schema.read(*realm);
383379

384-
realm->begin_transaction();
385-
perform_file_actions(*realm, file_manager);
386-
remove_dead_users(*realm, file_manager);
387-
realm->commit_transaction();
388-
389380
m_coordinator = _impl::RealmCoordinator::get_existing_coordinator(config.path);
381+
if (m_coordinator->try_claim_sync_agent()) {
382+
realm->begin_transaction();
383+
perform_file_actions(*realm, file_manager);
384+
remove_dead_users(*realm, file_manager);
385+
realm->commit_transaction();
386+
}
390387
}
391388

392389
std::shared_ptr<Realm> get_realm() const

test/object-store/sync/metadata.cpp

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,83 @@ TEST_CASE("app metadata: persisted", "[sync][metadata]") {
613613
}
614614
}
615615

616+
TEST_CASE("app metadata: multiple stores", "[sync][metadata]") {
617+
test_util::TestDirGuard test_dir(base_path);
618+
619+
AppConfig config;
620+
config.app_id = "app id";
621+
config.metadata_mode = AppConfig::MetadataMode::NoEncryption;
622+
config.base_file_path = base_path;
623+
SyncFileManager file_manager(config);
624+
625+
auto store_1 = create_metadata_store(config, file_manager);
626+
auto store_2 = create_metadata_store(config, file_manager);
627+
REQUIRE(store_1 != store_2);
628+
629+
SECTION("create user in one store then read from the other") {
630+
store_1->create_user(user_id, refresh_token, access_token, device_id);
631+
auto user = store_2->get_user(user_id);
632+
REQUIRE(user);
633+
}
634+
635+
SECTION("update existing user in one store then read from the other") {
636+
store_1->create_user(user_id, refresh_token, access_token, device_id);
637+
auto user_1 = store_1->get_user(user_id);
638+
639+
std::vector<UserIdentity> identities{{"identity", "provider"}};
640+
store_2->update_user(user_id, [&](UserData& data) {
641+
data.identities = identities;
642+
});
643+
644+
auto user_2 = store_1->get_user(user_id);
645+
CHECK(user_1->identities.empty());
646+
CHECK(user_2->identities == identities);
647+
}
648+
649+
SECTION("non-conflicting writes from multiple stores") {
650+
store_1->create_user(user_id, refresh_token, access_token, device_id);
651+
auto user_1 = store_1->get_user(user_id);
652+
auto user_2 = store_2->get_user(user_id);
653+
654+
UserProfile profile = bson::BsonDocument{{"name", "user's name"}, {"email", "user's email"}};
655+
std::vector<UserIdentity> identities{{"identity", "provider"}};
656+
store_1->update_user(user_id, [&](UserData& data) {
657+
data.identities = identities;
658+
});
659+
store_2->update_user(user_id, [&](UserData& data) {
660+
data.profile = profile;
661+
});
662+
663+
// The second write should not have discarded the change made by the first store
664+
user_1 = store_1->get_user(user_id);
665+
user_2 = store_2->get_user(user_id);
666+
REQUIRE(user_1->identities == user_2->identities);
667+
REQUIRE(user_1->identities == identities);
668+
REQUIRE(user_1->profile.data() == user_2->profile.data());
669+
REQUIRE(user_1->profile.data() == profile.data());
670+
}
671+
672+
SECTION("file actions are not performed on open if there is already a live store") {
673+
auto path = util::make_temp_file("file_to_delete");
674+
store_1->create_user(user_id, refresh_token, access_token, device_id);
675+
store_1->add_realm_path(user_id, path);
676+
store_1->log_out(user_id, SyncUser::State::Removed);
677+
678+
create_metadata_store(config, file_manager);
679+
REQUIRE(File::exists(path));
680+
681+
store_1.reset();
682+
683+
create_metadata_store(config, file_manager);
684+
REQUIRE(File::exists(path));
685+
686+
store_2.reset();
687+
688+
create_metadata_store(config, file_manager);
689+
REQUIRE_FALSE(File::exists(path));
690+
}
691+
}
692+
616693
#if REALM_ENABLE_ENCRYPTION
617694
TEST_CASE("app metadata: encryption", "[sync][metadata]") {
618695
test_util::TestDirGuard test_dir(base_path);
@@ -739,7 +816,7 @@ TEST_CASE("app metadata: encryption", "[sync][metadata]") {
739816
#endif // REALM_PLATFORM_APPLE
740817
}
741818

742-
#endif
819+
#endif // REALM_ENABLE_ENCRYPTION
743820

744821
#ifndef SWIFT_PACKAGE // The SPM build currently doesn't copy resource files
745822
TEST_CASE("sync metadata: can open old metadata realms", "[sync][metadata]") {

0 commit comments

Comments
 (0)