mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-26 22:48:20 +01:00
@@ -62,7 +62,7 @@ namespace Tgstation.Server.Api
|
||||
public static readonly Version Version = AssemblyName.Version.Semver();
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.Instance.Id"/> being accessed
|
||||
/// The instance <see cref="Models.EntityId.Id"/> being accessed
|
||||
/// </summary>
|
||||
public long? InstanceId { get; set; }
|
||||
|
||||
@@ -255,7 +255,7 @@ namespace Tgstation.Server.Api
|
||||
/// Set <see cref="HttpRequestHeaders"/> using the <see cref="ApiHeaders"/>. This initially clears <paramref name="headers"/>
|
||||
/// </summary>
|
||||
/// <param name="headers">The <see cref="HttpRequestHeaders"/> to set</param>
|
||||
/// <param name="instanceId">The <see cref="Models.Instance.Id"/> for the request</param>
|
||||
/// <param name="instanceId">The instance <see cref="Models.EntityId.Id"/> for the request</param>
|
||||
public void SetRequestHeaders(HttpRequestHeaders headers, long? instanceId = null)
|
||||
{
|
||||
if (headers == null)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Common base of <see cref="CompileJob"/>s and <see cref="Job"/>s.
|
||||
/// Common base of <see cref="Instance"/>s, <see cref="CompileJob"/>s, and <see cref="Job"/>s.
|
||||
/// </summary>
|
||||
public class EntityId
|
||||
{
|
||||
|
||||
@@ -6,13 +6,8 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// Metadata about a server instance
|
||||
/// </summary>
|
||||
public class Instance
|
||||
public class Instance : EntityId
|
||||
{
|
||||
/// <summary>
|
||||
/// The id of the <see cref="Instance"/>. Not modifiable
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="route">The route to run</param>
|
||||
/// <param name="body">The body of the request</param>
|
||||
/// <param name="method">The method of the request</param>
|
||||
/// <param name="instanceId">The optional <see cref="Instance.Id"/> for the request</param>
|
||||
/// <param name="instanceId">The optional instance <see cref="EntityId.Id"/> for the request</param>
|
||||
/// <param name="tokenRefresh">If this is a token refresh operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response on success</returns>
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace Tgstation.Server.Client
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="Api.Models.EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Create<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken);
|
||||
@@ -115,7 +115,7 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="Api.Models.EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Create<TResult>(string route, long instanceId, CancellationToken cancellationToken);
|
||||
@@ -125,7 +125,7 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="Api.Models.EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Patch<TResult>(string route, long instanceId, CancellationToken cancellationToken);
|
||||
@@ -135,7 +135,7 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="Api.Models.EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Read<TResult>(string route, long instanceId, CancellationToken cancellationToken);
|
||||
@@ -147,7 +147,7 @@ namespace Tgstation.Server.Client
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="Api.Models.EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Update<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken);
|
||||
@@ -156,7 +156,7 @@ namespace Tgstation.Server.Client
|
||||
/// Run an HTTP DELETE request
|
||||
/// </summary>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="Api.Models.EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Delete(string route, long instanceId, CancellationToken cancellationToken);
|
||||
@@ -167,7 +167,7 @@ namespace Tgstation.Server.Client
|
||||
/// <typeparam name="TBody">The type to of the request body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="Api.Models.EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Delete<TBody>(string route, TBody body, long instanceId, CancellationToken cancellationToken);
|
||||
@@ -177,7 +177,7 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="Api.Models.EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Delete<TResult>(string route, long instanceId, CancellationToken cancellationToken);
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Tgstation.Server.Client
|
||||
/// <summary>
|
||||
/// Create or attach an <paramref name="instance"/>
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="Instance"/> to create. <see cref="Instance.Id"/> will be ignored</param>
|
||||
/// <param name="instance">The <see cref="Instance"/> to create. <see cref="EntityId.Id"/> will be ignored</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the created or attached <see cref="Instance"/></returns>
|
||||
Task<Instance> CreateOrAttach(Instance instance, CancellationToken cancellationToken);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog.Context;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -111,6 +112,11 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// </summary>
|
||||
ulong channelIdCounter;
|
||||
|
||||
/// <summary>
|
||||
/// The number of <see cref="Message"/>s processed.
|
||||
/// </summary>
|
||||
long messagesProcessed;
|
||||
|
||||
/// <summary>
|
||||
/// If <see cref="StartAsync(CancellationToken)"/> has been called
|
||||
/// </summary>
|
||||
@@ -407,7 +413,9 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
foreach (var I in messageTasks.Where(x => x.Value.IsCompleted).ToList())
|
||||
{
|
||||
var message = await I.Value.ConfigureAwait(false);
|
||||
await ProcessMessage(I.Key, message, cancellationToken).ConfigureAwait(false);
|
||||
var messageNumber = Interlocked.Increment(ref messagesProcessed);
|
||||
using (LogContext.PushProperty("ChatMessage", messageNumber))
|
||||
await ProcessMessage(I.Key, message, cancellationToken).ConfigureAwait(false);
|
||||
messageTasks.Remove(I.Key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
public string Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Components.Chat.ChatUser"/> who sent the <see cref="Message"/>
|
||||
/// The <see cref="ChatUser"/> who sent the <see cref="Message"/>
|
||||
/// </summary>
|
||||
public ChatUser User { get; set; }
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <summary>
|
||||
/// For interacting with the instance services
|
||||
/// </summary>
|
||||
public interface IInstance : ILatestCompileJobProvider, IHostedService, IDisposable
|
||||
public interface IInstance : ILatestCompileJobProvider, IHostedService, IRenameNotifyee, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IRepositoryManager"/> for the <see cref="IInstance"/>
|
||||
@@ -45,12 +45,6 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
IConfiguration Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Rename the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
/// <param name="newName">The new name for the <see cref="IInstance"/></param>
|
||||
void Rename(string newName);
|
||||
|
||||
/// <summary>
|
||||
/// Change the <see cref="Api.Models.Instance.AutoUpdateInterval"/> for the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Handler for an instance being renamed.
|
||||
/// </summary>
|
||||
public interface IRenameNotifyee
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when the owning <see cref="Instance"/> is renamed.
|
||||
/// </summary>
|
||||
/// <param name="newInstanceName">The new <see cref="Api.Models.Instance.Name"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Context;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -134,12 +135,15 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
timerCts?.Dispose();
|
||||
Configuration.Dispose();
|
||||
Chat.Dispose();
|
||||
Watchdog.Dispose();
|
||||
dmbFactory.Dispose();
|
||||
RepositoryManager.Dispose();
|
||||
using (LogContext.PushProperty("Instance", metadata.Id))
|
||||
{
|
||||
timerCts?.Dispose();
|
||||
Configuration.Dispose();
|
||||
Chat.Dispose();
|
||||
Watchdog.Dispose();
|
||||
dmbFactory.Dispose();
|
||||
RepositoryManager.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -151,7 +155,7 @@ namespace Tgstation.Server.Host.Components
|
||||
#pragma warning disable CA1502 // TODO: Decomplexify
|
||||
async Task TimerLoop(uint minutes, CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogTrace("Entering auto-update loop");
|
||||
logger.LogDebug("Entering auto-update loop");
|
||||
while (true)
|
||||
try
|
||||
{
|
||||
@@ -393,17 +397,20 @@ namespace Tgstation.Server.Host.Components
|
||||
#pragma warning restore CA1502
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Rename(string newName)
|
||||
public Task InstanceRenamed(string newName, CancellationToken cancellationToken)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(newName))
|
||||
throw new ArgumentNullException(nameof(newName));
|
||||
metadata.Name = newName;
|
||||
return Watchdog.InstanceRenamed(newName, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.WhenAll(
|
||||
using (LogContext.PushProperty("Instance", metadata.Id))
|
||||
{
|
||||
await Task.WhenAll(
|
||||
SetAutoUpdateInterval(metadata.AutoUpdateInterval.Value),
|
||||
Configuration.StartAsync(cancellationToken),
|
||||
ByondManager.StartAsync(cancellationToken),
|
||||
@@ -411,23 +418,27 @@ namespace Tgstation.Server.Host.Components
|
||||
dmbFactory.StartAsync(cancellationToken))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// dependent on so many things, its just safer this way
|
||||
await Watchdog.StartAsync(cancellationToken).ConfigureAwait(false);
|
||||
// dependent on so many things, its just safer this way
|
||||
await Watchdog.StartAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await dmbFactory.CleanUnusedCompileJobs(cancellationToken).ConfigureAwait(false);
|
||||
await dmbFactory.CleanUnusedCompileJobs(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await SetAutoUpdateInterval(0).ConfigureAwait(false);
|
||||
await Watchdog.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
await Task.WhenAll(
|
||||
Configuration.StopAsync(cancellationToken),
|
||||
ByondManager.StopAsync(cancellationToken),
|
||||
Chat.StopAsync(cancellationToken),
|
||||
dmbFactory.StopAsync(cancellationToken))
|
||||
.ConfigureAwait(false);
|
||||
using (LogContext.PushProperty("Instance", metadata.Id))
|
||||
{
|
||||
await SetAutoUpdateInterval(0).ConfigureAwait(false);
|
||||
await Watchdog.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
await Task.WhenAll(
|
||||
Configuration.StopAsync(cancellationToken),
|
||||
ByondManager.StopAsync(cancellationToken),
|
||||
Chat.StopAsync(cancellationToken),
|
||||
dmbFactory.StopAsync(cancellationToken))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -438,8 +449,12 @@ namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
if (timerTask != null)
|
||||
{
|
||||
logger.LogTrace("Cancelling auto-update task");
|
||||
timerCts.Cancel();
|
||||
timerCts.Dispose();
|
||||
toWait = timerTask;
|
||||
timerTask = null;
|
||||
timerCts = null;
|
||||
}
|
||||
else
|
||||
toWait = Task.CompletedTask;
|
||||
@@ -447,13 +462,20 @@ namespace Tgstation.Server.Host.Components
|
||||
|
||||
await toWait.ConfigureAwait(false);
|
||||
if (newInterval == 0)
|
||||
{
|
||||
logger.LogTrace("New auto-update interval is 0. Not starting task.");
|
||||
return;
|
||||
}
|
||||
|
||||
lock (timerLock)
|
||||
{
|
||||
// race condition, just quit
|
||||
if (timerTask != null)
|
||||
{
|
||||
logger.LogDebug("Aborting auto update interval change due to race condition!");
|
||||
return;
|
||||
timerCts?.Dispose();
|
||||
}
|
||||
|
||||
timerCts = new CancellationTokenSource();
|
||||
timerTask = TimerLoop(newInterval, timerCts.Token);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Host.Components.Interop;
|
||||
using Tgstation.Server.Host.Components.Interop.Bridge;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
@@ -72,7 +73,7 @@ namespace Tgstation.Server.Host.Components
|
||||
readonly ILogger<InstanceManager> logger;
|
||||
|
||||
/// <summary>
|
||||
/// Map of <see cref="Api.Models.Instance.Id"/>s to respective <see cref="IInstance"/>s. Also used as a <see langword="lock"/> <see cref="object"/>.
|
||||
/// Map of instance <see cref="EntityId.Id"/>s to respective <see cref="IInstance"/>s. Also used as a <see langword="lock"/> <see cref="object"/>.
|
||||
/// </summary>
|
||||
readonly IDictionary<long, IInstance> instances;
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Interop.Bridge
|
||||
namespace Tgstation.Server.Host.Components.Interop.Bridge
|
||||
{
|
||||
/// <inheritdoc />
|
||||
interface IBridgeHandler : IBridgeDispatcher
|
||||
@@ -10,13 +7,5 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge
|
||||
/// The <see cref="DMApiParameters"/> for the <see cref="IBridgeHandler"/>.
|
||||
/// </summary>
|
||||
DMApiParameters DMApiParameters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the owning <see cref="Instance"/> is renamed.
|
||||
/// </summary>
|
||||
/// <param name="newInstanceName">The new <see cref="Api.Models.Instance.Name"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -120,5 +120,8 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Resume() => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
/// <summary>
|
||||
/// Handles communication with a DreamDaemon <see cref="IProcess"/>
|
||||
/// </summary>
|
||||
interface ISessionController : IProcessBase
|
||||
interface ISessionController : IRenameNotifyee, IProcessBase
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Task"/> that completes when DreamDaemon starts pumping the windows message queue after loading a .dmb or when it crashes
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Byond.TopicSender;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog.Context;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -105,6 +106,11 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
/// </summary>
|
||||
readonly ReattachInformation reattachInformation;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> metadata.
|
||||
/// </summary>
|
||||
readonly Api.Models.Instance metadata;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="CancellationTokenSource"/> used for the topic send operation made on reattaching.
|
||||
/// </summary>
|
||||
@@ -194,6 +200,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
/// Construct a <see cref="SessionController"/>
|
||||
/// </summary>
|
||||
/// <param name="reattachInformation">The value of <see cref="reattachInformation"/></param>
|
||||
/// <param name="metadata">The owning <see cref="Instance"/>.</param>
|
||||
/// <param name="process">The value of <see cref="process"/></param>
|
||||
/// <param name="byondLock">The value of <see cref="byondLock"/></param>
|
||||
/// <param name="byondTopicSender">The value of <see cref="byondTopicSender"/></param>
|
||||
@@ -206,6 +213,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
/// <param name="reattached">If this is a reattached session.</param>
|
||||
public SessionController(
|
||||
ReattachInformation reattachInformation,
|
||||
Api.Models.Instance metadata,
|
||||
IProcess process,
|
||||
IByondExecutableLock byondLock,
|
||||
ITopicClient byondTopicSender,
|
||||
@@ -218,6 +226,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
bool reattached)
|
||||
{
|
||||
this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation));
|
||||
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
|
||||
this.process = process ?? throw new ArgumentNullException(nameof(process));
|
||||
this.byondLock = byondLock ?? throw new ArgumentNullException(nameof(byondLock));
|
||||
this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender));
|
||||
@@ -364,141 +373,145 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
if (parameters == null)
|
||||
throw new ArgumentNullException(nameof(parameters));
|
||||
|
||||
var response = new BridgeResponse();
|
||||
switch (parameters.CommandType)
|
||||
using (LogContext.PushProperty("Instance", metadata.Id))
|
||||
{
|
||||
case BridgeCommandType.ChatSend:
|
||||
if (parameters.ChatMessage == null)
|
||||
return new BridgeResponse
|
||||
{
|
||||
ErrorMessage = "Missing chatMessage field!"
|
||||
};
|
||||
|
||||
if (parameters.ChatMessage.ChannelIds == null)
|
||||
return new BridgeResponse
|
||||
{
|
||||
ErrorMessage = "Missing channelIds field in chatMessage!"
|
||||
};
|
||||
|
||||
if(parameters.ChatMessage.ChannelIds.Any(channelIdString => !UInt64.TryParse(channelIdString, out var _)))
|
||||
return new BridgeResponse
|
||||
{
|
||||
ErrorMessage = "Invalid channelIds in chatMessage!"
|
||||
};
|
||||
|
||||
if (parameters.ChatMessage.Text == null)
|
||||
return new BridgeResponse
|
||||
{
|
||||
ErrorMessage = "Missing message field in chatMessage!"
|
||||
};
|
||||
|
||||
await chat.SendMessage(
|
||||
parameters.ChatMessage.Text,
|
||||
parameters.ChatMessage.ChannelIds.Select(UInt64.Parse),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
case BridgeCommandType.Prime:
|
||||
var oldPrimeTcs = primeTcs;
|
||||
primeTcs = new TaskCompletionSource<object>();
|
||||
oldPrimeTcs.SetResult(null);
|
||||
break;
|
||||
case BridgeCommandType.Kill:
|
||||
logger.LogInformation("Bridge requested process termination!");
|
||||
TerminationWasRequested = true;
|
||||
process.Terminate();
|
||||
break;
|
||||
case BridgeCommandType.PortUpdate:
|
||||
lock (synchronizationLock)
|
||||
{
|
||||
if (!parameters.CurrentPort.HasValue)
|
||||
{
|
||||
/////UHHHH
|
||||
logger.LogWarning("DreamDaemon sent new port command without providing it's own!");
|
||||
logger.LogTrace("Handling bridge request...");
|
||||
var response = new BridgeResponse();
|
||||
switch (parameters.CommandType)
|
||||
{
|
||||
case BridgeCommandType.ChatSend:
|
||||
if (parameters.ChatMessage == null)
|
||||
return new BridgeResponse
|
||||
{
|
||||
ErrorMessage = "Missing stringified port as data parameter!"
|
||||
ErrorMessage = "Missing chatMessage field!"
|
||||
};
|
||||
|
||||
if (parameters.ChatMessage.ChannelIds == null)
|
||||
return new BridgeResponse
|
||||
{
|
||||
ErrorMessage = "Missing channelIds field in chatMessage!"
|
||||
};
|
||||
|
||||
if (parameters.ChatMessage.ChannelIds.Any(channelIdString => !UInt64.TryParse(channelIdString, out var _)))
|
||||
return new BridgeResponse
|
||||
{
|
||||
ErrorMessage = "Invalid channelIds in chatMessage!"
|
||||
};
|
||||
|
||||
if (parameters.ChatMessage.Text == null)
|
||||
return new BridgeResponse
|
||||
{
|
||||
ErrorMessage = "Missing message field in chatMessage!"
|
||||
};
|
||||
|
||||
await chat.SendMessage(
|
||||
parameters.ChatMessage.Text,
|
||||
parameters.ChatMessage.ChannelIds.Select(UInt64.Parse),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
case BridgeCommandType.Prime:
|
||||
var oldPrimeTcs = primeTcs;
|
||||
primeTcs = new TaskCompletionSource<object>();
|
||||
oldPrimeTcs.SetResult(null);
|
||||
break;
|
||||
case BridgeCommandType.Kill:
|
||||
logger.LogInformation("Bridge requested process termination!");
|
||||
TerminationWasRequested = true;
|
||||
process.Terminate();
|
||||
break;
|
||||
case BridgeCommandType.PortUpdate:
|
||||
lock (synchronizationLock)
|
||||
{
|
||||
if (!parameters.CurrentPort.HasValue)
|
||||
{
|
||||
/////UHHHH
|
||||
logger.LogWarning("DreamDaemon sent new port command without providing it's own!");
|
||||
return new BridgeResponse
|
||||
{
|
||||
ErrorMessage = "Missing stringified port as data parameter!"
|
||||
};
|
||||
}
|
||||
|
||||
var currentPort = parameters.CurrentPort.Value;
|
||||
if (!nextPort.HasValue)
|
||||
reattachInformation.Port = parameters.CurrentPort.Value; // not ready yet, so what we'll do is accept the random port DD opened on for now and change it later when we decide to
|
||||
else
|
||||
{
|
||||
// nextPort is ready, tell DD to switch to that
|
||||
// if it fails it'll kill itself
|
||||
response.NewPort = nextPort.Value;
|
||||
reattachInformation.Port = nextPort.Value;
|
||||
nextPort = null;
|
||||
|
||||
// we'll also get here from SetPort so complete that task
|
||||
var tmpTcs = portAssignmentTcs;
|
||||
portAssignmentTcs = null;
|
||||
tmpTcs.SetResult(true);
|
||||
}
|
||||
|
||||
portClosedForReboot = false;
|
||||
}
|
||||
|
||||
var currentPort = parameters.CurrentPort.Value;
|
||||
if (!nextPort.HasValue)
|
||||
reattachInformation.Port = parameters.CurrentPort.Value; // not ready yet, so what we'll do is accept the random port DD opened on for now and change it later when we decide to
|
||||
else
|
||||
{
|
||||
// nextPort is ready, tell DD to switch to that
|
||||
// if it fails it'll kill itself
|
||||
response.NewPort = nextPort.Value;
|
||||
reattachInformation.Port = nextPort.Value;
|
||||
nextPort = null;
|
||||
break;
|
||||
case BridgeCommandType.Startup:
|
||||
apiValidationStatus = ApiValidationStatus.BadValidationRequest;
|
||||
if (parameters.Version == null)
|
||||
return new BridgeResponse
|
||||
{
|
||||
ErrorMessage = "Missing dmApiVersion field!"
|
||||
};
|
||||
|
||||
// we'll also get here from SetPort so complete that task
|
||||
var tmpTcs = portAssignmentTcs;
|
||||
portAssignmentTcs = null;
|
||||
tmpTcs.SetResult(true);
|
||||
DMApiVersion = parameters.Version;
|
||||
switch (parameters.MinimumSecurityLevel)
|
||||
{
|
||||
case DreamDaemonSecurity.Ultrasafe:
|
||||
apiValidationStatus = ApiValidationStatus.RequiresUltrasafe;
|
||||
break;
|
||||
case DreamDaemonSecurity.Safe:
|
||||
apiValidationStatus = ApiValidationStatus.RequiresSafe;
|
||||
break;
|
||||
case DreamDaemonSecurity.Trusted:
|
||||
apiValidationStatus = ApiValidationStatus.RequiresTrusted;
|
||||
break;
|
||||
case null:
|
||||
return new BridgeResponse
|
||||
{
|
||||
ErrorMessage = "Missing minimumSecurityLevel field!"
|
||||
};
|
||||
default:
|
||||
return new BridgeResponse
|
||||
{
|
||||
ErrorMessage = "Invalid minimumSecurityLevel!"
|
||||
};
|
||||
}
|
||||
|
||||
portClosedForReboot = false;
|
||||
}
|
||||
response.RuntimeInformation = reattachInformation.RuntimeInformation;
|
||||
|
||||
break;
|
||||
case BridgeCommandType.Startup:
|
||||
apiValidationStatus = ApiValidationStatus.BadValidationRequest;
|
||||
if (parameters.Version == null)
|
||||
return new BridgeResponse
|
||||
// Load custom commands
|
||||
chatTrackingContext.CustomCommands = parameters.CustomCommands;
|
||||
break;
|
||||
case BridgeCommandType.Reboot:
|
||||
if (ClosePortOnReboot)
|
||||
{
|
||||
ErrorMessage = "Missing dmApiVersion field!"
|
||||
};
|
||||
chatTrackingContext.Active = false;
|
||||
response.NewPort = 0;
|
||||
portClosedForReboot = true;
|
||||
}
|
||||
|
||||
DMApiVersion = parameters.Version;
|
||||
switch (parameters.MinimumSecurityLevel)
|
||||
{
|
||||
case DreamDaemonSecurity.Ultrasafe:
|
||||
apiValidationStatus = ApiValidationStatus.RequiresUltrasafe;
|
||||
break;
|
||||
case DreamDaemonSecurity.Safe:
|
||||
apiValidationStatus = ApiValidationStatus.RequiresSafe;
|
||||
break;
|
||||
case DreamDaemonSecurity.Trusted:
|
||||
apiValidationStatus = ApiValidationStatus.RequiresTrusted;
|
||||
break;
|
||||
case null:
|
||||
return new BridgeResponse
|
||||
{
|
||||
ErrorMessage = "Missing minimumSecurityLevel field!"
|
||||
};
|
||||
default:
|
||||
return new BridgeResponse
|
||||
{
|
||||
ErrorMessage = "Invalid minimumSecurityLevel!"
|
||||
};
|
||||
}
|
||||
var oldRebootTcs = rebootTcs;
|
||||
rebootTcs = new TaskCompletionSource<object>();
|
||||
oldRebootTcs.SetResult(null);
|
||||
break;
|
||||
case null:
|
||||
response.ErrorMessage = "Missing commandType!";
|
||||
break;
|
||||
default:
|
||||
response.ErrorMessage = "Requested commandType not supported!";
|
||||
break;
|
||||
}
|
||||
|
||||
response.RuntimeInformation = reattachInformation.RuntimeInformation;
|
||||
|
||||
// Load custom commands
|
||||
chatTrackingContext.CustomCommands = parameters.CustomCommands;
|
||||
break;
|
||||
case BridgeCommandType.Reboot:
|
||||
if (ClosePortOnReboot)
|
||||
{
|
||||
chatTrackingContext.Active = false;
|
||||
response.NewPort = 0;
|
||||
portClosedForReboot = true;
|
||||
}
|
||||
|
||||
var oldRebootTcs = rebootTcs;
|
||||
rebootTcs = new TaskCompletionSource<object>();
|
||||
oldRebootTcs.SetResult(null);
|
||||
break;
|
||||
case null:
|
||||
response.ErrorMessage = "Missing commandType!";
|
||||
break;
|
||||
default:
|
||||
response.ErrorMessage = "Requested commandType not supported!";
|
||||
break;
|
||||
return response;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -123,7 +123,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
|
||||
try
|
||||
{
|
||||
socket.Bind(new IPEndPoint(IPAddress.Loopback, port));
|
||||
socket.Bind(new IPEndPoint(IPAddress.Any, port));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -326,6 +326,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
|
||||
var sessionController = new SessionController(
|
||||
reattachInformation,
|
||||
instance,
|
||||
process,
|
||||
byondLock,
|
||||
byondTopicSender,
|
||||
@@ -391,6 +392,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
|
||||
var controller = new SessionController(
|
||||
reattachInformation,
|
||||
instance,
|
||||
process,
|
||||
byondLock,
|
||||
byondTopicSender,
|
||||
|
||||
@@ -346,5 +346,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
await LaunchNoLock(true, false, null, cancellationToken).ConfigureAwait(false);
|
||||
await chatTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken)
|
||||
=> Server?.InstanceRenamed(newInstanceName, cancellationToken) ?? Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,5 +583,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
Alpha = alphaServer?.Release(),
|
||||
Bravo = bravoServer?.Release()
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken)
|
||||
=> Task.WhenAll(
|
||||
alphaServer?.InstanceRenamed(newInstanceName, cancellationToken) ?? Task.CompletedTask,
|
||||
bravoServer?.InstanceRenamed(newInstanceName, cancellationToken) ?? Task.CompletedTask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <summary>
|
||||
/// Runs and monitors the twin server controllers
|
||||
/// </summary>
|
||||
public interface IWatchdog : IHostedService, IDisposable, IEventConsumer
|
||||
public interface IWatchdog : IHostedService, IDisposable, IEventConsumer, IRenameNotifyee
|
||||
{
|
||||
/// <summary>
|
||||
/// If the watchdog is running
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog.Context;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -289,10 +290,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
switch (++heartbeatsMissed)
|
||||
{
|
||||
case 1:
|
||||
Logger.LogDebug("DEFCON 4: Watchdog missed first heartbeat!");
|
||||
Logger.LogDebug("DEFCON 4: DreamDaemon missed first heartbeat!");
|
||||
break;
|
||||
case 2:
|
||||
var message2 = "DEFCON 3: Watchdog has missed 2 heartbeats!";
|
||||
var message2 = "DEFCON 3: DreamDaemon has missed 2 heartbeats!";
|
||||
Logger.LogInformation(message2);
|
||||
await Chat.SendWatchdogMessage(message2, true, cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
@@ -300,7 +301,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
var actionToTake = shouldShutdown
|
||||
? "shutdown"
|
||||
: "be restarted";
|
||||
var message3 = $"DEFCON 2: Watchdog has missed 3 heartbeats! If DreamDaemon does not respond to the next one, the watchdog will {actionToTake}!";
|
||||
var message3 = $"DEFCON 2: DreamDaemon has missed 3 heartbeats! If it does not respond to the next one, the watchdog will {actionToTake}!";
|
||||
Logger.LogWarning(message3);
|
||||
await Chat.SendWatchdogMessage(message3, false, cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
@@ -565,137 +566,138 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
try
|
||||
{
|
||||
for (var monitorState = new MonitorState(); monitorState.NextAction != MonitorAction.Exit; ++iteration)
|
||||
try
|
||||
{
|
||||
Logger.LogDebug("Iteration {0} of monitor loop", iteration);
|
||||
|
||||
// load the activation tasks into local variables
|
||||
var serverTasks = GetMonitoredServerTasks(monitorState);
|
||||
if (serverTasks.Count != 5)
|
||||
throw new InvalidOperationException("Expected 5 monitored server tasks!");
|
||||
|
||||
var activeServerLifetime = serverTasks[MonitorActivationReason.ActiveServerCrashed];
|
||||
var activeServerReboot = serverTasks[MonitorActivationReason.ActiveServerRebooted];
|
||||
var inactiveServerLifetime = serverTasks[MonitorActivationReason.InactiveServerCrashed];
|
||||
var inactiveServerReboot = serverTasks[MonitorActivationReason.InactiveServerRebooted];
|
||||
var inactiveStartupComplete = serverTasks[MonitorActivationReason.InactiveServerStartupComplete];
|
||||
|
||||
Task activeLaunchParametersChanged = ActiveParametersUpdated.Task;
|
||||
var newDmbAvailable = DmbFactory.OnNewerDmb;
|
||||
|
||||
var heartbeatSeconds = ActiveLaunchParameters.HeartbeatSeconds.Value;
|
||||
var heartbeat = heartbeatSeconds == 0
|
||||
? Extensions.TaskExtensions.InfiniteTask()
|
||||
: Task.Delay(TimeSpan.FromSeconds(heartbeatSeconds));
|
||||
|
||||
// cancel waiting if requested
|
||||
var cancelTcs = new TaskCompletionSource<object>();
|
||||
var toWaitOn = Task.WhenAny(
|
||||
activeServerLifetime,
|
||||
activeServerReboot,
|
||||
inactiveServerLifetime,
|
||||
inactiveServerReboot,
|
||||
inactiveStartupComplete,
|
||||
heartbeat,
|
||||
newDmbAvailable,
|
||||
cancelTcs.Task,
|
||||
activeLaunchParametersChanged);
|
||||
|
||||
// wait for something to happen
|
||||
using (cancellationToken.Register(() => cancelTcs.SetCanceled()))
|
||||
await toWaitOn.ConfigureAwait(false);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Logger.LogTrace("Monitor activated");
|
||||
|
||||
// always run HandleMonitorWakeup from the context of the semaphore lock
|
||||
using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false))
|
||||
using (LogContext.PushProperty("Monitor", iteration))
|
||||
try
|
||||
{
|
||||
// multiple things may have happened, handle them one at a time
|
||||
for (var moreActivationsToProcess = true; moreActivationsToProcess && (monitorState.NextAction == MonitorAction.Continue || monitorState.NextAction == MonitorAction.Skip);)
|
||||
{
|
||||
MonitorActivationReason activationReason = default; // this will always be assigned before being used
|
||||
Logger.LogTrace("Iteration {0} of monitor loop", iteration);
|
||||
|
||||
bool CheckActivationReason(ref Task task, MonitorActivationReason testActivationReason)
|
||||
// load the activation tasks into local variables
|
||||
var serverTasks = GetMonitoredServerTasks(monitorState);
|
||||
if (serverTasks.Count != 5)
|
||||
throw new InvalidOperationException("Expected 5 monitored server tasks!");
|
||||
|
||||
var activeServerLifetime = serverTasks[MonitorActivationReason.ActiveServerCrashed];
|
||||
var activeServerReboot = serverTasks[MonitorActivationReason.ActiveServerRebooted];
|
||||
var inactiveServerLifetime = serverTasks[MonitorActivationReason.InactiveServerCrashed];
|
||||
var inactiveServerReboot = serverTasks[MonitorActivationReason.InactiveServerRebooted];
|
||||
var inactiveStartupComplete = serverTasks[MonitorActivationReason.InactiveServerStartupComplete];
|
||||
|
||||
Task activeLaunchParametersChanged = ActiveParametersUpdated.Task;
|
||||
var newDmbAvailable = DmbFactory.OnNewerDmb;
|
||||
|
||||
var heartbeatSeconds = ActiveLaunchParameters.HeartbeatSeconds.Value;
|
||||
var heartbeat = heartbeatSeconds == 0
|
||||
? Extensions.TaskExtensions.InfiniteTask()
|
||||
: Task.Delay(TimeSpan.FromSeconds(heartbeatSeconds));
|
||||
|
||||
// cancel waiting if requested
|
||||
var cancelTcs = new TaskCompletionSource<object>();
|
||||
var toWaitOn = Task.WhenAny(
|
||||
activeServerLifetime,
|
||||
activeServerReboot,
|
||||
inactiveServerLifetime,
|
||||
inactiveServerReboot,
|
||||
inactiveStartupComplete,
|
||||
heartbeat,
|
||||
newDmbAvailable,
|
||||
cancelTcs.Task,
|
||||
activeLaunchParametersChanged);
|
||||
|
||||
// wait for something to happen
|
||||
using (cancellationToken.Register(() => cancelTcs.SetCanceled()))
|
||||
await toWaitOn.ConfigureAwait(false);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Logger.LogTrace("Monitor activated");
|
||||
|
||||
// always run HandleMonitorWakeup from the context of the semaphore lock
|
||||
using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
// multiple things may have happened, handle them one at a time
|
||||
for (var moreActivationsToProcess = true; moreActivationsToProcess && (monitorState.NextAction == MonitorAction.Continue || monitorState.NextAction == MonitorAction.Skip);)
|
||||
{
|
||||
var taskCompleted = task?.IsCompleted == true;
|
||||
task = null;
|
||||
if (monitorState.NextAction == MonitorAction.Skip)
|
||||
monitorState.NextAction = MonitorAction.Continue;
|
||||
else if (taskCompleted)
|
||||
MonitorActivationReason activationReason = default; // this will always be assigned before being used
|
||||
|
||||
bool CheckActivationReason(ref Task task, MonitorActivationReason testActivationReason)
|
||||
{
|
||||
activationReason = testActivationReason;
|
||||
return true;
|
||||
var taskCompleted = task?.IsCompleted == true;
|
||||
task = null;
|
||||
if (monitorState.NextAction == MonitorAction.Skip)
|
||||
monitorState.NextAction = MonitorAction.Continue;
|
||||
else if (taskCompleted)
|
||||
{
|
||||
activationReason = testActivationReason;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
// process the tasks in this order and call HandlerMonitorWakup for each depending on the new monitorState
|
||||
var anyActivation = CheckActivationReason(ref activeServerLifetime, MonitorActivationReason.ActiveServerCrashed)
|
||||
|| CheckActivationReason(ref activeServerReboot, MonitorActivationReason.ActiveServerRebooted)
|
||||
|| CheckActivationReason(ref newDmbAvailable, MonitorActivationReason.NewDmbAvailable)
|
||||
|| CheckActivationReason(ref inactiveServerLifetime, MonitorActivationReason.InactiveServerCrashed)
|
||||
|| CheckActivationReason(ref inactiveServerReboot, MonitorActivationReason.InactiveServerRebooted)
|
||||
|| CheckActivationReason(ref inactiveStartupComplete, MonitorActivationReason.InactiveServerStartupComplete)
|
||||
|| CheckActivationReason(ref activeLaunchParametersChanged, MonitorActivationReason.ActiveLaunchParametersUpdated)
|
||||
|| CheckActivationReason(ref heartbeat, MonitorActivationReason.Heartbeat);
|
||||
|
||||
// process the tasks in this order and call HandlerMonitorWakup for each depending on the new monitorState
|
||||
var anyActivation = CheckActivationReason(ref activeServerLifetime, MonitorActivationReason.ActiveServerCrashed)
|
||||
|| CheckActivationReason(ref activeServerReboot, MonitorActivationReason.ActiveServerRebooted)
|
||||
|| CheckActivationReason(ref newDmbAvailable, MonitorActivationReason.NewDmbAvailable)
|
||||
|| CheckActivationReason(ref inactiveServerLifetime, MonitorActivationReason.InactiveServerCrashed)
|
||||
|| CheckActivationReason(ref inactiveServerReboot, MonitorActivationReason.InactiveServerRebooted)
|
||||
|| CheckActivationReason(ref inactiveStartupComplete, MonitorActivationReason.InactiveServerStartupComplete)
|
||||
|| CheckActivationReason(ref activeLaunchParametersChanged, MonitorActivationReason.ActiveLaunchParametersUpdated)
|
||||
|| CheckActivationReason(ref heartbeat, MonitorActivationReason.Heartbeat);
|
||||
|
||||
if (!anyActivation)
|
||||
moreActivationsToProcess = false;
|
||||
else
|
||||
{
|
||||
Logger.LogTrace("Reason: {0}", activationReason);
|
||||
if (activationReason == MonitorActivationReason.Heartbeat)
|
||||
monitorState.NextAction = await HandleHeartbeat(
|
||||
monitorState.ActiveServer,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (!anyActivation)
|
||||
moreActivationsToProcess = false;
|
||||
else
|
||||
await HandleMonitorWakeup(
|
||||
activationReason,
|
||||
monitorState,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
{
|
||||
Logger.LogTrace("Reason: {0}", activationReason);
|
||||
if (activationReason == MonitorActivationReason.Heartbeat)
|
||||
monitorState.NextAction = await HandleHeartbeat(
|
||||
monitorState.ActiveServer,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
else
|
||||
await HandleMonitorWakeup(
|
||||
activationReason,
|
||||
monitorState,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogTrace("Next monitor action is to {0}", monitorState.NextAction);
|
||||
|
||||
// Restart if requested
|
||||
if (monitorState.NextAction == MonitorAction.Restart)
|
||||
monitorState = await MonitorRestart(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// let this bubble, other exceptions caught below
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// really, this should NEVER happen
|
||||
Logger.LogError(
|
||||
"Monitor crashed! Iteration: {0}, Monitor State: {1}, Exception: {2}",
|
||||
iteration,
|
||||
JsonConvert.SerializeObject(monitorState),
|
||||
e);
|
||||
|
||||
Logger.LogTrace("Next monitor action is to {0}", monitorState.NextAction);
|
||||
var nextActionMessage = monitorState.NextAction != MonitorAction.Exit
|
||||
? "Restarting"
|
||||
: "Shutting down";
|
||||
var chatTask = Chat.SendWatchdogMessage(
|
||||
$"Monitor crashed, this should NEVER happen! Please report this, full details in logs! {nextActionMessage}. Error: {e.Message}",
|
||||
false,
|
||||
cancellationToken);
|
||||
|
||||
// Restart if requested
|
||||
if (monitorState.NextAction == MonitorAction.Restart)
|
||||
monitorState = await MonitorRestart(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// let this bubble, other exceptions caught below
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// really, this should NEVER happen
|
||||
Logger.LogError(
|
||||
"Monitor crashed! Iteration: {0}, Monitor State: {1}, Exception: {2}",
|
||||
iteration,
|
||||
JsonConvert.SerializeObject(monitorState),
|
||||
e);
|
||||
if (disposed)
|
||||
monitorState.NextAction = MonitorAction.Exit;
|
||||
else if (monitorState.NextAction != MonitorAction.Exit)
|
||||
monitorState = await MonitorRestart(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var nextActionMessage = monitorState.NextAction != MonitorAction.Exit
|
||||
? "Restarting"
|
||||
: "Shutting down";
|
||||
var chatTask = Chat.SendWatchdogMessage(
|
||||
$"Monitor crashed, this should NEVER happen! Please report this, full details in logs! {nextActionMessage}. Error: {e.Message}",
|
||||
false,
|
||||
cancellationToken);
|
||||
|
||||
if (disposed)
|
||||
monitorState.NextAction = MonitorAction.Exit;
|
||||
else if (monitorState.NextAction != MonitorAction.Exit)
|
||||
monitorState = await MonitorRestart(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await chatTask.ConfigureAwait(false);
|
||||
}
|
||||
await chatTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -914,5 +916,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
if (Running)
|
||||
await Chat.SendWatchdogMessage("Detaching...", false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Context;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
@@ -161,26 +162,26 @@ namespace Tgstation.Server.Host.Controllers
|
||||
ModelState.Clear();
|
||||
}
|
||||
|
||||
if (ApiHeaders != null)
|
||||
Logger.LogDebug(
|
||||
"Request details: User ID {0}. Api version: {1}. User-Agent: {2}. Type: {3}. Route {4}{5} to Instance {6}",
|
||||
AuthenticationContext?.User.Id.Value.ToString(CultureInfo.InvariantCulture),
|
||||
ApiHeaders.ApiVersion.Semver(),
|
||||
ApiHeaders.RawUserAgent,
|
||||
Request.Method,
|
||||
Request.Path,
|
||||
Request.QueryString,
|
||||
ApiHeaders.InstanceId);
|
||||
|
||||
try
|
||||
using (ApiHeaders?.InstanceId != null
|
||||
? LogContext.PushProperty("Instance", ApiHeaders.InstanceId)
|
||||
: null)
|
||||
using (AuthenticationContext != null
|
||||
? LogContext.PushProperty("User", AuthenticationContext.User.Id)
|
||||
: null)
|
||||
using (LogContext.PushProperty("Request", $"{Request.Method} {Request.Path}"))
|
||||
{
|
||||
if (ApiHeaders != null)
|
||||
Logger.LogDebug(
|
||||
"Starting API Request: Version: {1}. User-Agent: {2}",
|
||||
AuthenticationContext?.User.Id.Value.ToString(CultureInfo.InvariantCulture),
|
||||
ApiHeaders.ApiVersion.Semver(),
|
||||
ApiHeaders.RawUserAgent,
|
||||
Request.Method,
|
||||
Request.Path,
|
||||
Request.QueryString,
|
||||
ApiHeaders.InstanceId);
|
||||
await base.OnActionExecutionAsync(context, next).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException e)
|
||||
{
|
||||
Logger.LogDebug("Request cancelled! Exception: {0}", e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1506
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog.Context;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
@@ -18,6 +20,11 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[Produces(ApiHeaders.ApplicationJson)]
|
||||
public class BridgeController : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Static counter for the number of requests processed.
|
||||
/// </summary>
|
||||
static long requestsProcessed;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IBridgeDispatcher"/> for the <see cref="BridgeController"/>
|
||||
/// </summary>
|
||||
@@ -32,11 +39,17 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// Initializes a new instance of the <see cref="BridgeController"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="bridgeDispatcher">The value of <see cref="bridgeDispatcher"/>.</param>
|
||||
/// <param name="applicationLifetime">The <see cref="IHostApplicationLifetime"/> of the server.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
public BridgeController(IBridgeDispatcher bridgeDispatcher, ILogger<BridgeController> logger)
|
||||
public BridgeController(IBridgeDispatcher bridgeDispatcher, IHostApplicationLifetime applicationLifetime, ILogger<BridgeController> logger)
|
||||
{
|
||||
this.bridgeDispatcher = bridgeDispatcher ?? throw new ArgumentNullException(nameof(bridgeDispatcher));
|
||||
if (applicationLifetime == null)
|
||||
throw new ArgumentNullException(nameof(applicationLifetime));
|
||||
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
applicationLifetime.ApplicationStopped.Register(() => requestsProcessed = 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -52,26 +65,29 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (!IPAddress.IsLoopback(Request.HttpContext.Connection.RemoteIpAddress))
|
||||
return NotFound();
|
||||
|
||||
BridgeParameters request;
|
||||
try
|
||||
using (LogContext.PushProperty("Bridge", Interlocked.Increment(ref requestsProcessed)))
|
||||
{
|
||||
request = JsonConvert.DeserializeObject<BridgeParameters>(data, DMApiConstants.SerializerSettings);
|
||||
BridgeParameters request;
|
||||
try
|
||||
{
|
||||
request = JsonConvert.DeserializeObject<BridgeParameters>(data, DMApiConstants.SerializerSettings);
|
||||
}
|
||||
catch
|
||||
{
|
||||
logger.LogWarning("Error deserializing bridge request: {0}", data);
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
logger.LogTrace("Bridge Request: {0}", data);
|
||||
|
||||
var response = await bridgeDispatcher.ProcessBridgeRequest(request, cancellationToken).ConfigureAwait(false);
|
||||
if (response == null)
|
||||
Forbid();
|
||||
|
||||
var responseJson = JsonConvert.SerializeObject(response, DMApiConstants.SerializerSettings);
|
||||
logger.LogTrace("Bridge Response: {0}", responseJson);
|
||||
return Content(responseJson, ApiHeaders.ApplicationJson);
|
||||
}
|
||||
catch
|
||||
{
|
||||
logger.LogWarning("Error deserializing bridge request: {0}", data);
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
logger.LogTrace("Bridge Request: {0}", data);
|
||||
|
||||
var response = await bridgeDispatcher.ProcessBridgeRequest(request, cancellationToken).ConfigureAwait(false);
|
||||
if (response == null)
|
||||
Forbid();
|
||||
|
||||
var responseJson = JsonConvert.SerializeObject(response, DMApiConstants.SerializerSettings);
|
||||
logger.LogTrace("Bridge Response: {0}", responseJson);
|
||||
return Content(responseJson, ApiHeaders.ApplicationJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// Detach an <see cref="Api.Models.Instance"/> with the given <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The <see cref="Api.Models.Instance.Id"/> to detach.</param>
|
||||
/// <param name="id">The <see cref="EntityId.Id"/> of the instance to detach.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the request.</returns>
|
||||
/// <response code="204">Instance detatched successfully.</response>
|
||||
@@ -475,7 +475,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (renamed)
|
||||
instanceManager.GetInstance(originalModel).Rename(originalModel.Name);
|
||||
await instanceManager.GetInstance(originalModel).InstanceRenamed(originalModel.Name, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var oldAutoStart = originalModel.DreamDaemonSettings.AutoStart;
|
||||
try
|
||||
@@ -582,7 +582,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// Get a specific <see cref="Api.Models.Instance"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The <see cref="Api.Models.Instance.Id"/> to retrieve.</param>
|
||||
/// <param name="id">The instance <see cref="EntityId.Id"/> to retrieve.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the request.</returns>
|
||||
/// <response code="200">Retrieved <see cref="Api.Models.Instance"/> successfully.</response>
|
||||
|
||||
@@ -133,10 +133,12 @@ namespace Tgstation.Server.Host.Core
|
||||
var logEventLevel = ConvertSeriLogLevel(postSetupServices.FileLoggingConfiguration.LogLevel);
|
||||
|
||||
var formatter = new MessageTemplateTextFormatter(
|
||||
"{Timestamp:o} {RequestId,13} [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}",
|
||||
"{Timestamp:o} "
|
||||
+ ServiceCollectionExtensions.SerilogContextTemplate
|
||||
+ ": [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}",
|
||||
null);
|
||||
|
||||
logPath = IOManager.ConcatPath(logPath, "tgs-{Date}.log");
|
||||
logPath = IOManager.ConcatPath(logPath, "tgs-.log");
|
||||
var rollingFileConfig = sinkConfig.File(
|
||||
formatter,
|
||||
logPath,
|
||||
@@ -167,6 +169,7 @@ namespace Tgstation.Server.Host.Core
|
||||
};
|
||||
});
|
||||
|
||||
// WARNING: STATIC CODE
|
||||
// fucking prevents converting 'sub' to M$ bs
|
||||
// can't be done in the above lambda, that's too late
|
||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
|
||||
|
||||
@@ -15,6 +15,11 @@ namespace Tgstation.Server.Host.Extensions
|
||||
/// </summary>
|
||||
static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Common template used for adding our custom log context to serilog.
|
||||
/// </summary>
|
||||
public const string SerilogContextTemplate = "(Instance:{Instance}|Job:{Job}|Request:{Request}|User:{User}|Monitor:{Monitor}|Bridge:{Bridge}|Chat:{ChatMessage})";
|
||||
|
||||
/// <summary>
|
||||
/// Add a standard <typeparamref name="TConfig"/> binding
|
||||
/// </summary>
|
||||
@@ -67,11 +72,14 @@ namespace Tgstation.Server.Host.Extensions
|
||||
configurationAction?.Invoke(configuration);
|
||||
|
||||
configuration
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo
|
||||
.Async(sinkConfiguration =>
|
||||
{
|
||||
sinkConfiguration.Console(
|
||||
outputTemplate: "[{Timestamp:HH:mm:ss}] {Level:w3}: {SourceContext:l}{NewLine} {Message:lj}{NewLine}{Exception}");
|
||||
outputTemplate: "[{Timestamp:HH:mm:ss}] {Level:w3}: {SourceContext:l} "
|
||||
+ SerilogContextTemplate
|
||||
+ "{NewLine} {Message:lj}{NewLine}{Exception}");
|
||||
sinkConfigurationAction?.Invoke(sinkConfiguration);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Context;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -77,67 +78,68 @@ namespace Tgstation.Server.Host.Jobs
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task RunJob(Job job, Func<Job, IDatabaseContextFactory, CancellationToken, Task> operation, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
void LogRegularException() => logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails);
|
||||
using (LogContext.PushProperty("Job", job.Id))
|
||||
try
|
||||
{
|
||||
var oldJob = job;
|
||||
job = new Job { Id = oldJob.Id };
|
||||
|
||||
await operation(job, databaseContextFactory, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
logger.LogDebug("Job {0} completed!", job.Id);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
logger.LogDebug("Job {0} cancelled!", job.Id);
|
||||
job.Cancelled = true;
|
||||
}
|
||||
catch (JobException e)
|
||||
{
|
||||
job.ErrorCode = e.ErrorCode;
|
||||
job.ExceptionDetails = e.Message;
|
||||
LogRegularException();
|
||||
if (e.InnerException != null)
|
||||
logger.LogDebug(
|
||||
"Inner exception for job {0}: {1}",
|
||||
job.Id,
|
||||
e.InnerException is JobException
|
||||
? e.InnerException.Message
|
||||
: e.InnerException.ToString());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
job.ExceptionDetails = e.ToString();
|
||||
LogRegularException();
|
||||
}
|
||||
|
||||
await databaseContextFactory.UseContext(async databaseContext =>
|
||||
{
|
||||
var attachedJob = new Job
|
||||
void LogRegularException() => logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails);
|
||||
try
|
||||
{
|
||||
Id = job.Id
|
||||
};
|
||||
var oldJob = job;
|
||||
job = new Job { Id = oldJob.Id };
|
||||
|
||||
databaseContext.Jobs.Attach(attachedJob);
|
||||
attachedJob.StoppedAt = DateTimeOffset.Now;
|
||||
attachedJob.ExceptionDetails = job.ExceptionDetails;
|
||||
attachedJob.ErrorCode = job.ErrorCode;
|
||||
attachedJob.Cancelled = job.Cancelled;
|
||||
await operation(job, databaseContextFactory, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await databaseContext.Save(default).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (synchronizationLock)
|
||||
{
|
||||
var handler = jobs[job.Id];
|
||||
jobs.Remove(job.Id);
|
||||
handler.Dispose();
|
||||
logger.LogDebug("Job {0} completed!", job.Id);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
logger.LogDebug("Job {0} cancelled!", job.Id);
|
||||
job.Cancelled = true;
|
||||
}
|
||||
catch (JobException e)
|
||||
{
|
||||
job.ErrorCode = e.ErrorCode;
|
||||
job.ExceptionDetails = e.Message;
|
||||
LogRegularException();
|
||||
if (e.InnerException != null)
|
||||
logger.LogDebug(
|
||||
"Inner exception for job {0}: {1}",
|
||||
job.Id,
|
||||
e.InnerException is JobException
|
||||
? e.InnerException.Message
|
||||
: e.InnerException.ToString());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
job.ExceptionDetails = e.ToString();
|
||||
LogRegularException();
|
||||
}
|
||||
|
||||
await databaseContextFactory.UseContext(async databaseContext =>
|
||||
{
|
||||
var attachedJob = new Job
|
||||
{
|
||||
Id = job.Id
|
||||
};
|
||||
|
||||
databaseContext.Jobs.Attach(attachedJob);
|
||||
attachedJob.StoppedAt = DateTimeOffset.Now;
|
||||
attachedJob.ExceptionDetails = job.ExceptionDetails;
|
||||
attachedJob.ErrorCode = job.ErrorCode;
|
||||
attachedJob.Cancelled = job.Cancelled;
|
||||
|
||||
await databaseContext.Save(default).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (synchronizationLock)
|
||||
{
|
||||
var handler = jobs[job.Id];
|
||||
jobs.Remove(job.Id);
|
||||
handler.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Models
|
||||
public const ushort DefaultChannelLimit = 100;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.Instance.Id"/>
|
||||
/// The instance <see cref="Api.Models.EntityId.Id"/>
|
||||
/// </summary>
|
||||
public long InstanceId { get; set; }
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Models
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.Instance.Id"/>
|
||||
/// The <see cref="Api.Models.EntityId.Id"/>
|
||||
/// </summary>
|
||||
public long InstanceId { get; set; }
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Models
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.Instance.Id"/>
|
||||
/// The instance <see cref="Api.Models.EntityId.Id"/>
|
||||
/// </summary>
|
||||
public long InstanceId { get; set; }
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.Instance.Id"/> of the <see cref="Instance"/> the <see cref="DualReattachInformation"/> belongs to
|
||||
/// The <see cref="Api.Models.EntityId.Id"/> of the <see cref="Instance"/> the <see cref="DualReattachInformation"/> belongs to
|
||||
/// </summary>
|
||||
public long InstanceId { get; set; }
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Models
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.Instance.Id"/> of <see cref="Instance"/>
|
||||
/// The <see cref="Api.Models.EntityId.Id"/> of <see cref="Instance"/>
|
||||
/// </summary>
|
||||
public long InstanceId { get; set; }
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Models
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.Instance.Id"/>
|
||||
/// The instance <see cref="EntityId.Id"/>
|
||||
/// </summary>
|
||||
public long InstanceId { get; set; }
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Models
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.Instance.Id"/>
|
||||
/// The instance <see cref="Api.Models.EntityId.Id"/>
|
||||
/// </summary>
|
||||
public long InstanceId { get; set; }
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Security
|
||||
/// Create an <see cref="IAuthenticationContext"/> to populate <see cref="CurrentAuthenticationContext"/>
|
||||
/// </summary>
|
||||
/// <param name="userId">The <see cref="Api.Models.Internal.User.Id"/> of the <see cref="IAuthenticationContext.User"/></param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> of the operation</param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.EntityId.Id"/> of the operation</param>
|
||||
/// <param name="validAfter">The <see cref="DateTimeOffset"/> the resulting <see cref="IAuthenticationContext.User"/>'s password must be valid after</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.8" />
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.3" />
|
||||
<PackageReference Include="Octokit" Version="0.47.0" />
|
||||
<PackageReference Include="Octokit" Version="0.48.0" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
|
||||
<!-- If this is updated, be sure to update the reference in the README.md -->
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" />
|
||||
|
||||
Reference in New Issue
Block a user