Fix race condition in multiprocess schema init
If the schema was initialized by a different process between when the old schema was read and the write transaction was began, the schema init code would see the updated schema version but not re-read the schema, resulting in it thinking that a migration was required when the schema actually matched.
This commit is contained in:
parent
415bfe4d35
commit
6c25eeb85c
|
@ -201,6 +201,7 @@ bool Realm::update_schema(std::unique_ptr<Schema> schema, uint64_t version)
|
||||||
{
|
{
|
||||||
schema->validate();
|
schema->validate();
|
||||||
|
|
||||||
|
// If the schema version matches, just verify that the schema itself also matches
|
||||||
bool needs_update = !m_config.read_only && (m_config.schema_version != version || ObjectStore::needs_update(*m_config.schema, *schema));
|
bool needs_update = !m_config.read_only && (m_config.schema_version != version || ObjectStore::needs_update(*m_config.schema, *schema));
|
||||||
if (!needs_update) {
|
if (!needs_update) {
|
||||||
ObjectStore::verify_schema(*m_config.schema, *schema, m_config.read_only);
|
ObjectStore::verify_schema(*m_config.schema, *schema, m_config.read_only);
|
||||||
|
@ -209,41 +210,64 @@ bool Realm::update_schema(std::unique_ptr<Schema> schema, uint64_t version)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the old config/schema for the migration function, and update
|
begin_transaction();
|
||||||
// our schema to the new one
|
struct WriteTransactionGuard {
|
||||||
auto old_schema = std::move(m_config.schema);
|
Realm& realm;
|
||||||
Config old_config(m_config);
|
~WriteTransactionGuard() {
|
||||||
old_config.read_only = true;
|
if (realm.is_in_transaction()) {
|
||||||
old_config.schema = std::move(old_schema);
|
realm.cancel_transaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} write_transaction_guard{*this};
|
||||||
|
|
||||||
m_config.schema = std::move(schema);
|
// Recheck the schema version after beginning the write transaction
|
||||||
m_config.schema_version = version;
|
// If it changed then someone else initialized the schema and we need to
|
||||||
|
// recheck everything
|
||||||
|
auto current_schema_version = ObjectStore::get_schema_version(read_group());
|
||||||
|
if (current_schema_version != m_config.schema_version) {
|
||||||
|
cancel_transaction();
|
||||||
|
|
||||||
|
m_config.schema_version = current_schema_version;
|
||||||
|
*m_config.schema = ObjectStore::schema_from_group(read_group());
|
||||||
|
return update_schema(std::move(schema), version);
|
||||||
|
}
|
||||||
|
|
||||||
auto migration_function = [&](Group*, Schema&) {
|
auto migration_function = [&](Group*, Schema&) {
|
||||||
SharedRealm old_realm(new Realm(old_config));
|
SharedRealm old_realm(new Realm(m_config));
|
||||||
auto updated_realm = shared_from_this();
|
old_realm->m_config.read_only = true;
|
||||||
if (m_config.migration_function) {
|
|
||||||
m_config.migration_function(old_realm, updated_realm);
|
auto new_realm = shared_from_this();
|
||||||
|
m_config.schema = std::move(schema);
|
||||||
|
m_config.schema_version = version;
|
||||||
|
|
||||||
|
if (!m_config.migration_function) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
m_config.migration_function(old_realm, new_realm);
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
m_config.schema = std::move(old_realm->m_config.schema);
|
||||||
|
m_config.schema_version = old_realm->m_config.schema_version;
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
bool changed = ObjectStore::update_realm_with_schema(read_group(), *m_config.schema,
|
||||||
// update and migrate
|
version, *schema,
|
||||||
begin_transaction();
|
migration_function);
|
||||||
bool changed = ObjectStore::update_realm_with_schema(read_group(), *old_config.schema,
|
commit_transaction();
|
||||||
version, *m_config.schema,
|
|
||||||
migration_function);
|
if (schema) {
|
||||||
commit_transaction();
|
// We update the schema after opening the "old" Realm in the migration
|
||||||
return changed;
|
// block to reduce the amount of juggling required, but that means that
|
||||||
}
|
// the schema hasn't been updated if no migration occurred
|
||||||
catch (...) {
|
m_config.schema = std::move(schema);
|
||||||
if (is_in_transaction()) {
|
m_config.schema_version = version;
|
||||||
cancel_transaction();
|
|
||||||
}
|
|
||||||
m_config.schema_version = old_config.schema_version;
|
|
||||||
m_config.schema = std::move(old_config.schema);
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void check_read_write(Realm *realm)
|
static void check_read_write(Realm *realm)
|
||||||
|
|
Loading…
Reference in New Issue