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:
commit
d78df0b084
@ -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,49 +278,93 @@ namespace osu.Game.Database
|
|||||||
Logger.Log(@"Recovery complete!", LoggingTarget.Database);
|
Logger.Log(@"Recovery complete!", LoggingTarget.Database);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cleanupPendingDeletions()
|
private Realm prepareFirstRealmAccess()
|
||||||
{
|
{
|
||||||
using (var realm = getRealmInstance())
|
string newerVersionFilename = $"{Filename.Replace(realm_extension, string.Empty)}_newer_version{realm_extension}";
|
||||||
using (var transaction = realm.BeginWrite())
|
|
||||||
|
// Attempt to recover a newer database version if available.
|
||||||
|
if (storage.Exists(newerVersionFilename))
|
||||||
{
|
{
|
||||||
var pendingDeleteScores = realm.All<ScoreInfo>().Where(s => s.DeletePending);
|
Logger.Log(@"A newer realm database has been found, attempting recovery...", LoggingTarget.Database);
|
||||||
|
attemptRecoverFromFile(newerVersionFilename);
|
||||||
foreach (var score in pendingDeleteScores)
|
|
||||||
realm.Remove(score);
|
|
||||||
|
|
||||||
var pendingDeleteSets = realm.All<BeatmapSetInfo>().Where(s => s.DeletePending);
|
|
||||||
|
|
||||||
foreach (var beatmapSet in pendingDeleteSets)
|
|
||||||
{
|
|
||||||
foreach (var beatmap in beatmapSet.Beatmaps)
|
|
||||||
{
|
|
||||||
// Cascade delete related scores, else they will have a null beatmap against the model's spec.
|
|
||||||
foreach (var score in beatmap.Scores)
|
|
||||||
realm.Remove(score);
|
|
||||||
|
|
||||||
realm.Remove(beatmap.Metadata);
|
|
||||||
realm.Remove(beatmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
realm.Remove(beatmapSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
var pendingDeleteSkins = realm.All<SkinInfo>().Where(s => s.DeletePending);
|
|
||||||
|
|
||||||
foreach (var s in pendingDeleteSkins)
|
|
||||||
realm.Remove(s);
|
|
||||||
|
|
||||||
var pendingDeletePresets = realm.All<ModPreset>().Where(s => s.DeletePending);
|
|
||||||
|
|
||||||
foreach (var s in pendingDeletePresets)
|
|
||||||
realm.Remove(s);
|
|
||||||
|
|
||||||
transaction.Commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean up files after dropping any pending deletions.
|
try
|
||||||
// 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();
|
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 transaction = realm.BeginWrite())
|
||||||
|
{
|
||||||
|
var pendingDeleteScores = realm.All<ScoreInfo>().Where(s => s.DeletePending);
|
||||||
|
|
||||||
|
foreach (var score in pendingDeleteScores)
|
||||||
|
realm.Remove(score);
|
||||||
|
|
||||||
|
var pendingDeleteSets = realm.All<BeatmapSetInfo>().Where(s => s.DeletePending);
|
||||||
|
|
||||||
|
foreach (var beatmapSet in pendingDeleteSets)
|
||||||
|
{
|
||||||
|
foreach (var beatmap in beatmapSet.Beatmaps)
|
||||||
|
{
|
||||||
|
// Cascade delete related scores, else they will have a null beatmap against the model's spec.
|
||||||
|
foreach (var score in beatmap.Scores)
|
||||||
|
realm.Remove(score);
|
||||||
|
|
||||||
|
realm.Remove(beatmap.Metadata);
|
||||||
|
realm.Remove(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
realm.Remove(beatmapSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pendingDeleteSkins = realm.All<SkinInfo>().Where(s => s.DeletePending);
|
||||||
|
|
||||||
|
foreach (var s in pendingDeleteSkins)
|
||||||
|
realm.Remove(s);
|
||||||
|
|
||||||
|
var pendingDeletePresets = realm.All<ModPreset>().Where(s => s.DeletePending);
|
||||||
|
|
||||||
|
foreach (var s in pendingDeletePresets)
|
||||||
|
realm.Remove(s);
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up files after dropping any pending deletions.
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
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>
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user