1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 13:22:55 +08:00

Merge pull request #23695 from peppy/realm-startup-error-handling

Adjust realm startup for added reliability
This commit is contained in:
Bartłomiej Dach 2023-05-31 22:46:58 +02:00 committed by GitHub
commit d78df0b084
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -179,43 +179,9 @@ namespace osu.Game.Database
applyFilenameSchemaSuffix(ref Filename); applyFilenameSchemaSuffix(ref Filename);
#endif #endif
string newerVersionFilename = $"{Filename.Replace(realm_extension, string.Empty)}_newer_version{realm_extension}"; // `prepareFirstRealmAccess()` triggers the first `getRealmInstance` call, which will implicitly run realm migrations and bring the schema up-to-date.
using (var realm = prepareFirstRealmAccess())
// Attempt to recover a newer database version if available. cleanupPendingDeletions(realm);
if (storage.Exists(newerVersionFilename))
{
Logger.Log(@"A newer realm database has been found, attempting recovery...", LoggingTarget.Database);
attemptRecoverFromFile(newerVersionFilename);
}
try
{
// This method triggers the first `getRealmInstance` call, which will implicitly run realm migrations and bring the schema up-to-date.
cleanupPendingDeletions();
}
catch (Exception e)
{
// See https://github.com/realm/realm-core/blob/master/src%2Frealm%2Fobject-store%2Fobject_store.cpp#L1016-L1022
// This is the best way we can detect a schema version downgrade.
if (e.Message.StartsWith(@"Provided schema version", StringComparison.Ordinal))
{
Logger.Error(e, "Your local database is too new to work with this version of osu!. Please close osu! and install the latest release to recover your data.");
// If a newer version database already exists, don't backup again. We can presume that the first backup is the one we care about.
if (!storage.Exists(newerVersionFilename))
createBackup(newerVersionFilename);
storage.Delete(Filename);
}
else
{
Logger.Error(e, "Realm startup failed with unrecoverable error; starting with a fresh database. A backup of your database has been made.");
createBackup($"{Filename.Replace(realm_extension, string.Empty)}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}_corrupt{realm_extension}");
storage.Delete(Filename);
}
cleanupPendingDeletions();
}
} }
/// <summary> /// <summary>
@ -312,9 +278,48 @@ namespace osu.Game.Database
Logger.Log(@"Recovery complete!", LoggingTarget.Database); Logger.Log(@"Recovery complete!", LoggingTarget.Database);
} }
private void cleanupPendingDeletions() private Realm prepareFirstRealmAccess()
{
string newerVersionFilename = $"{Filename.Replace(realm_extension, string.Empty)}_newer_version{realm_extension}";
// Attempt to recover a newer database version if available.
if (storage.Exists(newerVersionFilename))
{
Logger.Log(@"A newer realm database has been found, attempting recovery...", LoggingTarget.Database);
attemptRecoverFromFile(newerVersionFilename);
}
try
{
return getRealmInstance();
}
catch (Exception e)
{
// See https://github.com/realm/realm-core/blob/master/src%2Frealm%2Fobject-store%2Fobject_store.cpp#L1016-L1022
// This is the best way we can detect a schema version downgrade.
if (e.Message.StartsWith(@"Provided schema version", StringComparison.Ordinal))
{
Logger.Error(e, "Your local database is too new to work with this version of osu!. Please close osu! and install the latest release to recover your data.");
// If a newer version database already exists, don't backup again. We can presume that the first backup is the one we care about.
if (!storage.Exists(newerVersionFilename))
createBackup(newerVersionFilename);
}
else
{
Logger.Error(e, "Realm startup failed with unrecoverable error; starting with a fresh database. A backup of your database has been made.");
createBackup($"{Filename.Replace(realm_extension, string.Empty)}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}_corrupt{realm_extension}");
}
storage.Delete(Filename);
return getRealmInstance();
}
}
private void cleanupPendingDeletions(Realm realm)
{
try
{ {
using (var realm = getRealmInstance())
using (var transaction = realm.BeginWrite()) using (var transaction = realm.BeginWrite())
{ {
var pendingDeleteScores = realm.All<ScoreInfo>().Where(s => s.DeletePending); var pendingDeleteScores = realm.All<ScoreInfo>().Where(s => s.DeletePending);
@ -356,6 +361,11 @@ namespace osu.Game.Database
// in the future we may want to only do this when the game is idle, rather than on every startup. // in the future we may want to only do this when the game is idle, rather than on every startup.
new RealmFileStore(this, storage).Cleanup(); new RealmFileStore(this, storage).Cleanup();
} }
catch (Exception e)
{
Logger.Error(e, "Failed to clean up unused files. This is not critical but please report if it happens regularly.");
}
}
/// <summary> /// <summary>
/// Compact this realm. /// Compact this realm.
@ -909,7 +919,7 @@ namespace osu.Game.Database
int attempts = 10; int attempts = 10;
while (attempts-- > 0) while (true)
{ {
try try
{ {
@ -927,6 +937,9 @@ namespace osu.Game.Database
} }
catch (IOException) catch (IOException)
{ {
if (attempts-- <= 0)
throw;
// file may be locked during use. // file may be locked during use.
Thread.Sleep(500); Thread.Sleep(500);
} }