Fix multiple issues causing database reset to fail

This commit is contained in:
Dean Herbert 2018-06-04 02:07:02 +09:00
parent 68910745d8
commit 3a823d6c25
3 changed files with 44 additions and 18 deletions

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<OsuDbContext>(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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}