mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 15:44:04 +09:00
DatabaseWriteUsage
This commit is contained in:
@ -1,10 +1,8 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using osu.Framework.Platform;
|
||||
|
||||
@ -17,9 +15,7 @@ namespace osu.Game.Database
|
||||
/// <summary>
|
||||
/// Create a new <see cref="OsuDbContext"/> instance (separate from the shared context via <see cref="GetContext"/> for performing isolated operations.
|
||||
/// </summary>
|
||||
protected readonly Func<OsuDbContext> CreateContext;
|
||||
|
||||
private readonly ThreadLocal<OsuDbContext> queryContext;
|
||||
protected readonly DatabaseContextFactory ContextFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Refresh an instance potentially from a different thread with a local context-tracked instance.
|
||||
@ -29,33 +25,27 @@ namespace osu.Game.Database
|
||||
/// <typeparam name="T">A valid EF-stored type.</typeparam>
|
||||
protected virtual void Refresh<T>(ref T obj, IEnumerable<T> lookupSource = null) where T : class, IHasPrimaryKey
|
||||
{
|
||||
var context = GetContext();
|
||||
|
||||
if (context.Entry(obj).State != EntityState.Detached) return;
|
||||
|
||||
var id = obj.ID;
|
||||
var foundObject = lookupSource?.SingleOrDefault(t => t.ID == id) ?? context.Find<T>(id);
|
||||
if (foundObject != null)
|
||||
using (var usage = ContextFactory.GetForWrite())
|
||||
{
|
||||
obj = foundObject;
|
||||
context.Entry(obj).Reload();
|
||||
var context = usage.Context;
|
||||
|
||||
if (context.Entry(obj).State != EntityState.Detached) return;
|
||||
|
||||
var id = obj.ID;
|
||||
var foundObject = lookupSource?.SingleOrDefault(t => t.ID == id) ?? context.Find<T>(id);
|
||||
if (foundObject != null)
|
||||
{
|
||||
obj = foundObject;
|
||||
context.Entry(obj).Reload();
|
||||
}
|
||||
else
|
||||
context.Add(obj);
|
||||
}
|
||||
else
|
||||
context.Add(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a shared context for performing lookups (or write operations on the update thread, for now).
|
||||
/// </summary>
|
||||
protected OsuDbContext GetContext() => queryContext.Value;
|
||||
|
||||
protected DatabaseBackedStore(Func<OsuDbContext> createContext, Storage storage = null)
|
||||
protected DatabaseBackedStore(DatabaseContextFactory contextFactory, Storage storage = null)
|
||||
{
|
||||
CreateContext = createContext;
|
||||
|
||||
// todo: while this seems to work quite well, we need to consider that contexts could enter a state where they are never cleaned up.
|
||||
queryContext = new ThreadLocal<OsuDbContext>(CreateContext);
|
||||
|
||||
ContextFactory = contextFactory;
|
||||
Storage = storage;
|
||||
}
|
||||
|
||||
|
@ -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.Threading;
|
||||
using osu.Framework.Platform;
|
||||
|
||||
namespace osu.Game.Database
|
||||
@ -11,17 +12,70 @@ namespace osu.Game.Database
|
||||
|
||||
private const string database_name = @"client";
|
||||
|
||||
private ThreadLocal<OsuDbContext> threadContexts;
|
||||
|
||||
private readonly object writeLock = new object();
|
||||
|
||||
private OsuDbContext writeContext;
|
||||
|
||||
private volatile int currentWriteUsages;
|
||||
|
||||
public DatabaseContextFactory(GameHost host)
|
||||
{
|
||||
this.host = host;
|
||||
recycleThreadContexts();
|
||||
}
|
||||
|
||||
public OsuDbContext GetContext() => new OsuDbContext(host.Storage.GetDatabaseConnectionString(database_name));
|
||||
/// <summary>
|
||||
/// Get a context for read-only usage.
|
||||
/// </summary>
|
||||
public OsuDbContext Get() => threadContexts.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context).
|
||||
/// This method may block if a write is already active on a different thread.
|
||||
/// </summary>
|
||||
/// <returns>A usage containing a usable context.</returns>
|
||||
public DatabaseWriteUsage GetForWrite()
|
||||
{
|
||||
lock (writeLock)
|
||||
{
|
||||
var usage = new DatabaseWriteUsage(writeContext ?? (writeContext = threadContexts.Value), usageCompleted);
|
||||
Interlocked.Increment(ref currentWriteUsages);
|
||||
return usage;
|
||||
}
|
||||
}
|
||||
|
||||
private void usageCompleted(DatabaseWriteUsage usage)
|
||||
{
|
||||
int usages = Interlocked.Decrement(ref currentWriteUsages);
|
||||
if (usages == 0)
|
||||
{
|
||||
writeContext.Dispose();
|
||||
writeContext = null;
|
||||
|
||||
// once all writes are complete, we want to refresh thread-specific contexts to make sure they don't have stale local caches.
|
||||
recycleThreadContexts();
|
||||
}
|
||||
}
|
||||
|
||||
private void recycleThreadContexts() => threadContexts = new ThreadLocal<OsuDbContext>(CreateContext);
|
||||
|
||||
protected virtual OsuDbContext CreateContext()
|
||||
{
|
||||
var ctx = new OsuDbContext(host.Storage.GetDatabaseConnectionString(database_name));
|
||||
ctx.Database.AutoTransactionsEnabled = false;
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public void ResetDatabase()
|
||||
{
|
||||
// todo: we probably want to make sure there are no active contexts before performing this operation.
|
||||
host.Storage.DeleteDatabase(database_name);
|
||||
lock (writeLock)
|
||||
{
|
||||
recycleThreadContexts();
|
||||
host.Storage.DeleteDatabase(database_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
28
osu.Game/Database/DatabaseWriteUsage.cs
Normal file
28
osu.Game/Database/DatabaseWriteUsage.cs
Normal file
@ -0,0 +1,28 @@
|
||||
// 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 Microsoft.EntityFrameworkCore.Storage;
|
||||
|
||||
namespace osu.Game.Database
|
||||
{
|
||||
public class DatabaseWriteUsage : IDisposable
|
||||
{
|
||||
public readonly OsuDbContext Context;
|
||||
private readonly IDbContextTransaction transaction;
|
||||
private readonly Action<DatabaseWriteUsage> usageCompleted;
|
||||
|
||||
public DatabaseWriteUsage(OsuDbContext context, Action<DatabaseWriteUsage> onCompleted)
|
||||
{
|
||||
Context = context;
|
||||
transaction = Context.BeginTransaction();
|
||||
usageCompleted = onCompleted;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Context.SaveChanges(transaction);
|
||||
usageCompleted?.Invoke(this);
|
||||
}
|
||||
}
|
||||
}
|
21
osu.Game/Database/SingletonContextFactory.cs
Normal file
21
osu.Game/Database/SingletonContextFactory.cs
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Database
|
||||
{
|
||||
public class SingletonContextFactory : DatabaseContextFactory
|
||||
{
|
||||
private readonly OsuDbContext context;
|
||||
|
||||
public SingletonContextFactory(OsuDbContext context)
|
||||
: base(null)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
protected override OsuDbContext CreateContext()
|
||||
{
|
||||
return context;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user