Pull the chains slightly tighter

This commit is contained in:
Cyberboss
2018-05-04 16:25:12 -04:00
parent 523e19fbf7
commit dd6dd65824
8 changed files with 53 additions and 21 deletions
@@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using System;
using System.Linq;
using System.Threading;
@@ -12,7 +11,7 @@ namespace Tgstation.Server.Host.Components
/// <summary>
/// Standard <see cref="IDmbFactory"/>
/// </summary>
sealed class DmbFactory : IDmbFactory, ICompileJobConsumer, IHostedService, IDisposable
sealed class DmbFactory : IDmbFactory, ICompileJobConsumer
{
/// <summary>
/// The <see cref="IDatabaseContext"/> for the <see cref="DmbFactory"/>
@@ -102,7 +101,12 @@ namespace Tgstation.Server.Host.Components
else
task = newerDmbTcs.Task;
return await task.ConfigureAwait(false);
var result = await task.ConfigureAwait(false);
//so there's currently a race condition in DreamMakerController where the setting of CompileJob.RevisionInformation and thus IDmbProvider.RevisionInformation can be delayed to after this if someone tries to start the server instantly after compiling
//This is a terrible terrible hack to get around that
//I'm sorry future me, I can't think of any other way to fix this other than giving DreamMaker an IDatabaseContext or having the controller load the CompileJob
await Task.Delay(new TimeSpan(0, 0, 10), cancellationToken).ConfigureAwait(false);
return result;
}
/// <inheritdoc />
@@ -47,6 +47,10 @@ namespace Tgstation.Server.Host.Components
/// The <see cref="IInterop"/> for <see cref="DreamMaker"/>
/// </summary>
readonly IInterop interop;
/// <summary>
/// The <see cref="ICompileJobConsumer"/> for <see cref="DreamMaker"/>
/// </summary>
readonly ICompileJobConsumer compileJobConsumer;
/// <summary>
/// Construct <see cref="DreamMaker"/>
@@ -56,13 +60,15 @@ namespace Tgstation.Server.Host.Components
/// <param name="dreamDaemonExecutor">The value of <see cref="dreamDaemonExecutor"/></param>
/// <param name="byond">The value of <see cref="byond"/></param>
/// <param name="interop">The value of <see cref="interop"/></param>
public DreamMaker(IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop)
/// <param name="compileJobConsumer">The value of <see cref="compileJobConsumer"/></param>
public DreamMaker(IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop, ICompileJobConsumer compileJobConsumer)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
this.dreamDaemonExecutor = dreamDaemonExecutor ?? throw new ArgumentNullException(nameof(dreamDaemonExecutor));
this.byond = byond ?? throw new ArgumentNullException(nameof(byond));
this.interop = interop ?? throw new ArgumentNullException(nameof(interop));
this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer));
}
/// <summary>
@@ -248,6 +254,7 @@ namespace Tgstation.Server.Host.Components
await configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken).ConfigureAwait(false);
await symATask.ConfigureAwait(false);
}
compileJobConsumer.LoadCompileJob(job);
return job;
}
catch
@@ -1,10 +1,10 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using System;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Components
{
interface ICompileJobConsumer
interface ICompileJobConsumer : IHostedService, IDisposable
{
void LoadCompileJob(CompileJob job);
}
@@ -11,7 +11,8 @@ namespace Tgstation.Server.Host.Components
/// Create an <see cref="IInstance"/>
/// </summary>
/// <param name="metadata">The <see cref="Host.Models.Instance"/></param>
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the operation</param>
/// <returns>A new <see cref="IInstance"/></returns>
IInstance CreateInstance(Host.Models.Instance metadata);
IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext);
}
}
@@ -1,5 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Components
{
@@ -19,9 +20,10 @@ namespace Tgstation.Server.Host.Components
/// Online an <see cref="IInstance"/>
/// </summary>
/// <param name="metadata">The <see cref="Host.Models.Instance"/> of the desired <see cref="IInstance"/></param>
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the operation</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task OnlineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken);
Task OnlineInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext, CancellationToken cancellationToken);
/// <summary>
/// Offline an <see cref="IInstance"/>
@@ -35,9 +37,10 @@ namespace Tgstation.Server.Host.Components
/// Move an <see cref="IInstance"/>
/// </summary>
/// <param name="metadata">The <see cref="Host.Models.Instance"/> of the desired <see cref="IInstance"/></param>
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the operation</param>
/// <param name="newPath">The new path of the <see cref="IInstance"/>. <paramref name="metadata"/> will have this set on <see cref="Api.Models.Instance.Path"/> if the operation completes successfully</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task MoveInstance(Host.Models.Instance metadata, string newPath, CancellationToken cancellationToken);
Task MoveInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext, string newPath, CancellationToken cancellationToken);
}
}
@@ -17,10 +17,12 @@ namespace Tgstation.Server.Host.Components
public IChat Chat { get; }
public IConfiguration Configuration { get; }
readonly ICompileJobConsumer compileJobConsumer;
readonly Api.Models.Instance metadata;
public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IDreamDaemon dreamDaemon, IChat chat, IConfiguration configuration)
public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IDreamDaemon dreamDaemon, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer)
{
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager));
@@ -29,6 +31,7 @@ namespace Tgstation.Server.Host.Components
DreamDaemon = dreamDaemon ?? throw new ArgumentNullException(nameof(dreamDaemon));
Chat = chat ?? throw new ArgumentNullException(nameof(chat));
Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer));
}
public Api.Models.Instance GetMetadata() => metadata.CloneMetadata();
@@ -40,8 +43,8 @@ namespace Tgstation.Server.Host.Components
metadata.Name = newName;
}
public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StartAsync(cancellationToken), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken));
public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StartAsync(cancellationToken), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken));
public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StopAsync(cancellationToken), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken));
public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StopAsync(cancellationToken), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken));
}
}
@@ -1,16 +1,25 @@
using System;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Components
{
/// <inheritdoc />
sealed class InstanceFactory : IInstanceFactory
{
/// <summary>
/// The <see cref="IIOManager"/> for the <see cref="InstanceFactory"/>
/// </summary>
readonly IIOManager ioManager;
/// <summary>
/// Construct an <see cref="InstanceFactory"/>
/// </summary>
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
public InstanceFactory(IIOManager ioManager) => this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
/// <inheritdoc />
public IInstance CreateInstance(Host.Models.Instance metadata)
public IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext)
{
//Create the ioManager for the instance
@@ -21,6 +30,11 @@ namespace Tgstation.Server.Host.Components
var byondIOManager = new ResolvingIOManager(instanceIoManager, "Byond");
var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game");
var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration");
var codeModificationsIoMananger = new ResolvingIOManager(instanceIoManager, "CodeModifications");
var dmbFactory = new DmbFactory(databaseContext, gameIoManager);
throw new NotImplementedException();
}
@@ -60,7 +60,7 @@ namespace Tgstation.Server.Host.Components
}
/// <inheritdoc />
public async Task MoveInstance(Host.Models.Instance instance, string newPath, CancellationToken cancellationToken)
public async Task MoveInstance(Host.Models.Instance instance, IDatabaseContext databaseContext, string newPath, CancellationToken cancellationToken)
{
if (newPath == null)
throw new ArgumentNullException(nameof(newPath));
@@ -72,14 +72,14 @@ namespace Tgstation.Server.Host.Components
var oldPath = instance.Path;
await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(false);
instance.Path = ioManager.ResolvePath(newPath);
instanceOnlineTask = OnlineInstance(instance, default);
instanceOnlineTask = OnlineInstance(instance, databaseContext, default);
await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(false);
}
finally
{
if (instance.Online)
if (instanceOnlineTask == null)
await OnlineInstance(instance, default).ConfigureAwait(false);
await OnlineInstance(instance, databaseContext, default).ConfigureAwait(false);
else
await instanceOnlineTask.ConfigureAwait(false);
}
@@ -101,11 +101,11 @@ namespace Tgstation.Server.Host.Components
}
/// <inheritdoc />
public async Task OnlineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken)
public async Task OnlineInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext, CancellationToken cancellationToken)
{
if (metadata == null)
throw new ArgumentNullException(nameof(metadata));
var instance = instanceFactory.CreateInstance(metadata);
var instance = instanceFactory.CreateInstance(metadata, databaseContext);
lock (this)
{
if (instances.ContainsKey(metadata.Id))
@@ -124,7 +124,7 @@ namespace Tgstation.Server.Host.Components
await databaseContext.Initialize(cancellationToken).ConfigureAwait(false);
var dbInstances = databaseContext.Instances.Where(x => x.Online).Include(x => x.RepositorySettings).Include(x => x.ChatSettings).Include(x => x.DreamDaemonSettings).ToAsyncEnumerable();
var tasks = new List<Task>();
await dbInstances.ForEachAsync(metadata => tasks.Add(OnlineInstance(metadata, cancellationToken)), cancellationToken).ConfigureAwait(false);
await dbInstances.ForEachAsync(metadata => tasks.Add(OnlineInstance(metadata, databaseContext, cancellationToken)), cancellationToken).ConfigureAwait(false);
await Task.WhenAll(tasks).ConfigureAwait(false);
}
}