From 246a4a4bfe3abb8219965c55a9bd56c0317953d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 16:05:12 +0900 Subject: [PATCH 1/2] Add support for starting with a fresh realm database if the existing one is not usable The most common scenario is switching between schema versions when testing. This should alleviate the manual overhead of that for the majority of cases. For users, this will show a notification on startup if their database was purged, similar to what we had with EF. --- osu.Game/Database/RealmContextFactory.cs | 37 +++++++++++++++++++----- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 8548e63e94..e6fe5dfc54 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -105,8 +105,20 @@ namespace osu.Game.Database if (!Filename.EndsWith(realm_extension, StringComparison.Ordinal)) Filename += realm_extension; - // This method triggers the first `CreateContext` call, which will implicitly run realm migrations and bring the schema up-to-date. - cleanupPendingDeletions(); + try + { + // This method triggers the first `CreateContext` call, which will implicitly run realm migrations and bring the schema up-to-date. + cleanupPendingDeletions(); + } + catch (Exception e) + { + 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(); + } } private void cleanupPendingDeletions() @@ -396,14 +408,23 @@ namespace osu.Game.Database const int sleep_length = 200; int timeout = 5000; - // see https://github.com/realm/realm-dotnet/discussions/2657 - while (!Compact()) + try { - Thread.Sleep(sleep_length); - timeout -= sleep_length; + // see https://github.com/realm/realm-dotnet/discussions/2657 + while (!Compact()) + { + Thread.Sleep(sleep_length); + timeout -= sleep_length; - if (timeout < 0) - throw new TimeoutException(@"Took too long to acquire lock"); + if (timeout < 0) + throw new TimeoutException(@"Took too long to acquire lock"); + } + } + catch (Exception e) + { + // Compact may fail if the realm is in a bad state. + // We still want to continue with the blocking operation, though. + Logger.Log($"Realm compact failed with error {e}", LoggingTarget.Database); } } catch From 261b4988f9552b63a951efe3041e34a0b1f377a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jan 2022 10:58:59 +0900 Subject: [PATCH 2/2] Only catch `RealmExceptions` to avoid blocking the nested `TimeoutException` --- osu.Game/Database/RealmContextFactory.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 52f515a4bb..901f28a449 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -21,6 +21,7 @@ using osu.Game.Stores; using osu.Game.Rulesets; using osu.Game.Scoring; using Realms; +using Realms.Exceptions; #nullable enable @@ -427,7 +428,7 @@ namespace osu.Game.Database throw new TimeoutException(@"Took too long to acquire lock"); } } - catch (Exception e) + catch (RealmException e) { // Compact may fail if the realm is in a bad state. // We still want to continue with the blocking operation, though.