diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index a1d371f431..b5378b1311 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Linq; using System.Threading; using Microsoft.EntityFrameworkCore.Storage; @@ -46,20 +47,35 @@ namespace osu.Game.Database public DatabaseWriteUsage GetForWrite(bool withTransaction = true) { Monitor.Enter(writeLock); + OsuDbContext context; - if (currentWriteTransaction == null && withTransaction) + try { - // this mitigates the fact that changes on tracked entities will not be rolled back with the transaction by ensuring write operations are always executed in isolated contexts. - // if this results in sub-optimal efficiency, we may need to look into removing Database-level transactions in favour of running SaveChanges where we currently commit the transaction. - if (threadContexts.IsValueCreated) - recycleThreadContexts(); + if (currentWriteTransaction == null && withTransaction) + { + // this mitigates the fact that changes on tracked entities will not be rolled back with the transaction by ensuring write operations are always executed in isolated contexts. + // if this results in sub-optimal efficiency, we may need to look into removing Database-level transactions in favour of running SaveChanges where we currently commit the transaction. + if (threadContexts.IsValueCreated) + recycleThreadContexts(); - currentWriteTransaction = threadContexts.Value.Database.BeginTransaction(); + context = threadContexts.Value; + currentWriteTransaction = context.Database.BeginTransaction(); + } + else + { + context = threadContexts.Value; + } + } + catch (Exception e) + { + // retrieval of a context could trigger a fatal error. + Monitor.Exit(writeLock); + throw; } Interlocked.Increment(ref currentWriteUsages); - return new DatabaseWriteUsage(threadContexts.Value, usageCompleted) { IsTransactionLeader = currentWriteTransaction != null && currentWriteUsages == 1 }; + return new DatabaseWriteUsage(context, usageCompleted) { IsTransactionLeader = currentWriteTransaction != null && currentWriteUsages == 1 }; } private void usageCompleted(DatabaseWriteUsage usage) @@ -100,19 +116,18 @@ namespace osu.Game.Database private void recycleThreadContexts() => threadContexts = new ThreadLocal(CreateContext); - protected virtual OsuDbContext CreateContext() + protected virtual OsuDbContext CreateContext() => new OsuDbContext(host.Storage.GetDatabaseConnectionString(database_name)) { - var ctx = new OsuDbContext(host.Storage.GetDatabaseConnectionString(database_name)); - ctx.Database.AutoTransactionsEnabled = false; - - return ctx; - } + Database = { AutoTransactionsEnabled = false } + }; public void ResetDatabase() { lock (writeLock) { recycleThreadContexts(); + GC.Collect(); + GC.WaitForPendingFinalizers(); host.Storage.DeleteDatabase(database_name); } } diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 7758b3eb25..4b0de57c4c 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -58,11 +58,20 @@ namespace osu.Game.Database this.connectionString = connectionString; var connection = Database.GetDbConnection(); - connection.Open(); - using (var cmd = connection.CreateCommand()) + try { - cmd.CommandText = "PRAGMA journal_mode=WAL;"; - cmd.ExecuteNonQuery(); + connection.Open(); + + using (var cmd = connection.CreateCommand()) + { + cmd.CommandText = "PRAGMA journal_mode=WAL;"; + cmd.ExecuteNonQuery(); + } + } + catch (Exception e) + { + connection.Close(); + throw; } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index b9d32a6322..f6b3935689 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -211,15 +211,17 @@ namespace osu.Game using (var db = contextFactory.GetForWrite(false)) db.Context.Migrate(); } - catch (MigrationFailedException e) + catch (Exception e) { Logger.Error(e.InnerException ?? e, "Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database); // if we failed, let's delete the database and start fresh. // todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this. contextFactory.ResetDatabase(); + Logger.Log("Database purged successfully.", LoggingTarget.Database, LogLevel.Important); + // only run once more, then hard bail. using (var db = contextFactory.GetForWrite(false)) db.Context.Migrate(); }