diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm
index e8ba604067..981a30e1ad 100644
--- a/src/DMAPI/tgs/v4/api.dm
+++ b/src/DMAPI/tgs/v4/api.dm
@@ -2,11 +2,11 @@
#define TGS4_TOPIC_COMMAND "tgs_com"
#define TGS4_TOPIC_TOKEN "tgs_tok"
-#define TGS4_TOPIC_SUCCESS "tgs_success"
#define TGS4_TOPIC_SWAP "tgs_swap"
#define TGS4_TOPIC_SWAP_DELAYED "tgs_swap_delayed"
#define TGS4_TOPIC_CHAT_COMMAND "tgs_chat_comm"
#define TGS4_TOPIC_EVENT "tgs_event"
+#define TGS4_TOPIC_IDENTIFY "tgs_ident"
#define TGS4_COMM_VALIDATE "tgs_vali"
#define TGS4_COMM_SERVER_PRIMED "tgs_prime"
@@ -23,6 +23,8 @@
var/port_2
var/cached_json
var/json_path
+
+ var/list/intercepted_message_queue
var/list/custom_commands
@@ -95,8 +97,14 @@
return json_encode(list("error" = "Error running chat command!"))
return result
if(TGS4_TOPIC_EVENT)
+ intercepted_message_queue = list()
event_handler.HandleEvent(text2num(params[TGS4_TOPIC_EVENT]))
- return TGS4_TOPIC_SUCCESS
+ . = json_encode(intercepted_message_queue)
+ intercepted_message_queue = null
+ return
+ if(TGS4_TOPIC_IDENTIFY)
+ //they want to know our initial port
+ return "[port_1]"
return "Unknown command: [command]"
@@ -104,13 +112,16 @@
set waitfor = FALSE
var/new_port = world.port == port_1 ? port_2 : port_1
event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port)
- world.OpenPort("none") //close the port
if(delayed)
+ world.OpenPort("none") //close the port
sleep(50) //wait for other server to close port
- world.OpenPort(new_port)
+
+ //do NOT give up, if we remain unresponsive we will be killed
+ while(!world.OpenPort(new_port))
+ sleep(10)
/datum/tgs_api/v4/proc/Export(command)
- return world.Export("[host_path]/Interop/[instance_id]?command=[url_encode(command)]&access_token=[access_token]")
+ return file2text(world.Export("[host_path]/Interop/[instance_id]?command=[url_encode(command)]&access_token=[access_token]")["CONTENT"])
/datum/tgs_api/v4/OnReboot()
var/json = Export(TGS4_COMM_SERVER_REBOOT)
@@ -163,15 +174,24 @@
var/datum/tgs_chat_channel/channel = I
ids += channel.id
message = list("message" = message, "channels" = ids)
- Export("[TGS4_COMM_CHAT] [json_encode(message)]")
+ if(interepted_message_queue)
+ interepted_message_queue += list(message)
+ else
+ Export("[TGS4_COMM_CHAT] [json_encode(message)]")
/datum/tgs_api/v4/ChatTargetedBroadcast(message, admin_only)
message = list("message" = message, "channels" = admin_only ? "admin" : "game")
- Export("[TGS4_COMM_CHAT] [json_encode(message)]")
+ if(interepted_message_queue)
+ interepted_message_queue += list(message)
+ else
+ Export("[TGS4_COMM_CHAT] [json_encode(message)]")
/datum/tgs_api/v4/ChatPrivateMessage(message, datum/tgs_chat_user/user)
message = list("message" = message, "user" = list("id" = user.id, "channel" = user.channel.id))
- Export("[TGS4_COMM_CHAT] [json_encode(message)]")
+ if(interepted_message_queue)
+ interepted_message_queue += list(message)
+ else
+ Export("[TGS4_COMM_CHAT] [json_encode(message)]")
/datum/tgs_api/v4/ChatChannelInfo()
. = list()
diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs
index d31366048f..0833d79e34 100644
--- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs
@@ -26,14 +26,14 @@ namespace Tgstation.Server.Api.Models.Internal
public DateTimeOffset FinishedAt { get; set; }
///
- /// The detected DMAPI version
+ /// If the DMAPI version detected is compatible
///
- public Version DMApiVersion { get; set; }
+ public bool DMApiValidated { get; set; }
///
/// The .dme file used for compilation
///
- public string DmePath { get; set; }
+ public string DmeName { get; set; }
///
/// Textual output of DM
@@ -43,7 +43,7 @@ namespace Tgstation.Server.Api.Models.Internal
///
/// The Game folder the results were compiled into
///
- public Guid? OutputGuid { get; set; }
+ public Guid? DirectoryName { get; set; }
///
/// Exit code of DM. If
diff --git a/src/Tgstation.Server.Host/Components/DreamDaemon.cs b/src/Tgstation.Server.Host/Components/DreamDaemon.cs
index 04055e8757..1d6846ccb3 100644
--- a/src/Tgstation.Server.Host/Components/DreamDaemon.cs
+++ b/src/Tgstation.Server.Host/Components/DreamDaemon.cs
@@ -68,9 +68,7 @@ namespace Tgstation.Server.Host.Components
this.interop = interop ?? throw new ArgumentNullException(nameof(interop));
this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog));
currentLaunchParameters = initialSettings ?? throw new ArgumentNullException(nameof(initialSettings));
-
- interop.SetServerControlHandler(OnServerControl);
-
+
autoStart = initialSettings.AutoStart;
semaphore = new SemaphoreSlim(1);
@@ -79,21 +77,6 @@ namespace Tgstation.Server.Host.Components
///
public void Dispose() => semaphore.Dispose();
- ///
- /// Handler for server control events
- ///
- /// The
- /// The for the operation
- /// A representing the running operation
- static async Task OnServerControl(ServerControlEvent serverControlEventArgs, CancellationToken cancellationToken)
- {
- if (serverControlEventArgs == null)
- throw new ArgumentNullException(nameof(serverControlEventArgs));
-
- await Task.Yield();
- throw new NotImplementedException();
- }
-
///
public async Task CancelGracefulActions(CancellationToken cancellationToken)
{
diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs
new file mode 100644
index 0000000000..4abd5e10b5
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs
@@ -0,0 +1,276 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Tgstation.Server.Api.Models;
+using Tgstation.Server.Api.Models.Internal;
+using Tgstation.Server.Host.Components.Models;
+using Tgstation.Server.Host.Core;
+using Tgstation.Server.Host.Security;
+
+namespace Tgstation.Server.Host.Components
+{
+ ///
+ sealed class DreamMaker : IDreamMaker
+ {
+ ///
+ /// Name of the primary directory used for compilation
+ ///
+ const string ADirectoryName = "A";
+
+ ///
+ /// The for
+ ///
+ readonly IRepositoryManager repositoryManager;
+ ///
+ /// The for
+ ///
+ readonly IIOManager ioManager;
+ ///
+ /// The for
+ ///
+ readonly IConfiguration configuration;
+ ///
+ /// The for
+ ///
+ readonly IDreamDaemonExecutor dreamDaemonExecutor;
+ ///
+ /// The for
+ ///
+ readonly IByond byond;
+ ///
+ /// The for
+ ///
+ readonly IInterop interop;
+ ///
+ /// The for
+ ///
+ readonly ICryptographySuite cryptographySuite;
+
+ ///
+ /// Construct
+ ///
+ /// The value of
+ /// The value of
+ /// The value of
+ /// The value of
+ /// The value of
+ /// The value of
+ /// The value of
+ public DreamMaker(IRepositoryManager repositoryManager, IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop, ICryptographySuite cryptographySuite)
+ {
+ this.repositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager));
+ 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.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
+ }
+
+ ///
+ /// Run a quick DD instance to test the DMAPI is installed on the target code
+ ///
+ /// The path to the DreamDaemon executable
+ /// The for the operation
+ /// The for the operation
+ /// A resulting in if the DMAPI was successfully validated, otherwise
+ async Task VerifyApi(string dreamDaemonPath, Host.Models.CompileJob job, CancellationToken cancellationToken)
+ {
+ var launchParameters = new DreamDaemonLaunchParameters
+ {
+ AllowWebClient = false,
+ PrimaryPort = 0, //pick any port
+ SecurityLevel = DreamDaemonSecurity.Safe //all it needs to read the file and exit
+ };
+
+ using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
+ {
+ var interopInfo = new InteropInfo
+ {
+ ApiValidateOnly = true,
+ HostPath = Application.HostingPath,
+ AccessToken = cryptographySuite.GetSecureString()
+ };
+
+ using (var control = interop.CreateRun(launchParameters.PrimaryPort, null, null))
+ {
+ var ddTcs = new TaskCompletionSource
/// The return type of
- /// A taking the path to either dm.exe or dreamdaemon.exe and returning a
+ /// A taking the path to either dm.exe and dreamdaemon.exe and returning a
/// Use the staged installation if possible
- /// Pass the path of dreamdaemon.exe to if dm.exe otherwise
/// A resulting in the return type of
- Task UseExecutable(Func> operation, bool stagedIfExists, bool dreamDaemon);
+ Task UseExecutables(Func> operation, bool stagedIfExists);
///
/// Clears the cache folder
diff --git a/src/Tgstation.Server.Host/Components/IDmbFactory.cs b/src/Tgstation.Server.Host/Components/IDmbFactory.cs
index 2ed1a771ff..19ca0dc1ae 100644
--- a/src/Tgstation.Server.Host/Components/IDmbFactory.cs
+++ b/src/Tgstation.Server.Host/Components/IDmbFactory.cs
@@ -1,5 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
+using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Components
{
diff --git a/src/Tgstation.Server.Host/Components/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/IDreamMaker.cs
index 9f119ac27d..8e6036dc55 100644
--- a/src/Tgstation.Server.Host/Components/IDreamMaker.cs
+++ b/src/Tgstation.Server.Host/Components/IDreamMaker.cs
@@ -8,14 +8,14 @@ namespace Tgstation.Server.Host.Components
///
/// For managing the compiler
///
- interface IDreamMaker : IHostedService
+ interface IDreamMaker
{
///
/// Starts a compile
///
- /// The .dme file to use
+ /// The .dme file to use without the extension
/// The for the operation
- /// A resulting in the partially populated for the operation
- Task Compile(string dmePath, CancellationToken cancellationToken);
+ /// A resulting in the partially populated for the operation. In particular, note the field will only have it's field populated
+ Task Compile(string dmeName, CancellationToken cancellationToken);
}
}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Host/Components/IInterop.cs b/src/Tgstation.Server.Host/Components/IInterop.cs
index 32edd2e047..ff5337dc30 100644
--- a/src/Tgstation.Server.Host/Components/IInterop.cs
+++ b/src/Tgstation.Server.Host/Components/IInterop.cs
@@ -9,18 +9,8 @@ namespace Tgstation.Server.Host.Components
///
interface IInterop
{
- bool SecondaryIsOther { get; set; }
+ IInteropControl ReconnectToRun(string primaryAccessToken, string secondaryAccessToken, ushort primaryPort, ushort secondaryPort);
- void SetServerControlHandler(Func serverControlHandler);
- void SetChatMessageHandler(Func chatMessageHandler);
-
- Task GetApiVersion(CancellationToken cancellationToken);
-
- void SetRun(ushort? port, string accessToken, bool primary);
-
- Task ActivateOtherServer(CancellationToken cancellationToken);
-
- Task ChatCommand(string command, string arguments, CancellationToken cancellationToken);
- void OnServerPrimed(Action actionToTake);
+ IInteropControl CreateRun(ushort primaryPort, ushort? secondaryPort, Func chatMessageHandler);
}
}
diff --git a/src/Tgstation.Server.Host/Components/IInteropControl.cs b/src/Tgstation.Server.Host/Components/IInteropControl.cs
new file mode 100644
index 0000000000..2fc3f5ea6d
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/IInteropControl.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Tgstation.Server.Host.Components
+{
+ ///
+ /// Represents a control set for one or two DreamDaemon instances
+ ///
+ interface IInteropControl : IDisposable
+ {
+ ///
+ /// When a server control message arrives
+ ///
+ event EventHandler OnServerControl;
+
+ bool TwinServerMode { get; }
+
+ bool SecondaryIsOther { get; }
+
+ string PrimaryAccessToken { get; }
+
+ string SecondaryAccessToken { get; }
+
+ Task ActivateOtherServer(CancellationToken cancellationToken);
+
+ Task ChangePort(ushort newPort, bool forPrimary, CancellationToken cancellationToken);
+ }
+}
diff --git a/src/Tgstation.Server.Host/Components/IRepository.cs b/src/Tgstation.Server.Host/Components/IRepository.cs
index e52261b3dc..7acd5053df 100644
--- a/src/Tgstation.Server.Host/Components/IRepository.cs
+++ b/src/Tgstation.Server.Host/Components/IRepository.cs
@@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
+using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Components
{
diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs
index ce439ee988..454b1063de 100644
--- a/src/Tgstation.Server.Host/Components/Instance.cs
+++ b/src/Tgstation.Server.Host/Components/Instance.cs
@@ -40,8 +40,8 @@ namespace Tgstation.Server.Host.Components
metadata.Name = newName;
}
- public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StartAsync(cancellationToken), DreamMaker.StartAsync(cancellationToken), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken));
+ public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StartAsync(cancellationToken), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken));
- public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StopAsync(cancellationToken), DreamMaker.StopAsync(cancellationToken), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken));
+ public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StopAsync(cancellationToken), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken));
}
}
diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs
index 59f4ee867c..ed93f70fa9 100644
--- a/src/Tgstation.Server.Host/Components/InstanceManager.cs
+++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs
@@ -49,6 +49,8 @@ namespace Tgstation.Server.Host.Components
///
public IInstance GetInstance(Host.Models.Instance metadata)
{
+ if (metadata == null)
+ throw new ArgumentNullException(nameof(metadata));
lock (this)
{
if (!instances.TryGetValue(metadata.Id, out IInstance instance))
@@ -86,6 +88,8 @@ namespace Tgstation.Server.Host.Components
///
public async Task OfflineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken)
{
+ if (metadata == null)
+ throw new ArgumentNullException(nameof(metadata));
IInstance instance;
lock (this)
{
@@ -99,6 +103,8 @@ namespace Tgstation.Server.Host.Components
///
public async Task OnlineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken)
{
+ if (metadata == null)
+ throw new ArgumentNullException(nameof(metadata));
var instance = instanceFactory.CreateInstance(metadata);
lock (this)
{
@@ -133,6 +139,10 @@ namespace Tgstation.Server.Host.Components
///
public Task PreserveActiveExecutablesIfNecessary(DreamDaemonLaunchParameters launchParameters, string accessToken, int pid, bool primary)
{
+ if (launchParameters == null)
+ throw new ArgumentNullException(nameof(launchParameters));
+ if (accessToken == null)
+ throw new ArgumentNullException(nameof(accessToken));
throw new NotImplementedException();
}
}
diff --git a/src/Tgstation.Server.Host/Components/Models/TestMerge.cs b/src/Tgstation.Server.Host/Components/Models/TestMerge.cs
index 1996ad5bc3..cda4aaa5ea 100644
--- a/src/Tgstation.Server.Host/Components/Models/TestMerge.cs
+++ b/src/Tgstation.Server.Host/Components/Models/TestMerge.cs
@@ -2,6 +2,9 @@
namespace Tgstation.Server.Host.Components.Models
{
+ ///
+ /// This model mirrors /datum/tgs_revision_information/test_merge
+ ///
sealed class TestMerge : RevisionInformation
{
public int Number { get; set; }
diff --git a/src/Tgstation.Server.Host/Components/ServerControlEventType.cs b/src/Tgstation.Server.Host/Components/ServerControlEventType.cs
index 11a9e23bac..9468bcabaa 100644
--- a/src/Tgstation.Server.Host/Components/ServerControlEventType.cs
+++ b/src/Tgstation.Server.Host/Components/ServerControlEventType.cs
@@ -16,6 +16,10 @@
///
/// The server requested that it's process be terminated
///
- RequestedProcessTermination
+ RequestedProcessTermination,
+ ///
+ /// The server has stopped responding to heartbeats
+ ///
+ ServerUnresponsive,
}
}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs b/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs
new file mode 100644
index 0000000000..389256d050
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/TemporaryDmbProvider.cs
@@ -0,0 +1,37 @@
+using System;
+using Tgstation.Server.Host.Models;
+
+namespace Tgstation.Server.Host.Components
+{
+ ///
+ /// Temporary
+ ///
+ sealed class TemporaryDmbProvider : IDmbProvider
+ {
+ ///
+ public string DmbName { get; }
+
+ ///
+ public string PrimaryDirectory { get; }
+
+ ///
+ public string SecondaryDirectory => throw new NotSupportedException();
+
+ ///
+ public RevisionInformation RevisionInformation => null;
+
+ ///
+ /// Construct a
+ ///
+ /// The value of
+ /// The value of
+ public TemporaryDmbProvider(string directory, string dmb)
+ {
+ DmbName = dmb ?? throw new ArgumentNullException(nameof(dmb));
+ PrimaryDirectory = directory ?? throw new ArgumentNullException(nameof(directory));
+ }
+
+ ///
+ public void Dispose() { }
+ }
+}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Host/Components/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog.cs
index f7eaf3d327..a98ce38c27 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog.cs
@@ -4,7 +4,6 @@ using System.Threading.Tasks;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Components.Models;
using Tgstation.Server.Host.Core;
-using Tgstation.Server.Host.Security;
namespace Tgstation.Server.Host.Components
{
@@ -32,10 +31,6 @@ namespace Tgstation.Server.Host.Components
///
readonly IDmbFactory dmbFactory;
///
- /// The for the
- ///
- readonly ICryptographySuite cryptographySuite;
- ///
/// The for the
///
readonly IEventConsumer eventConsumer;
@@ -65,18 +60,16 @@ namespace Tgstation.Server.Host.Components
/// The value of
/// The value of
/// The value of
- /// The value of
/// The value of
/// The value of
/// The value of
- public Watchdog(IByond byond, IChat chat, IDreamDaemonExecutor dreamDaemonExecutor, IInterop interop, IDmbFactory dmbFactory, ICryptographySuite cryptographySuite, IEventConsumer eventConsumer, IInstanceManager instanceManager, long instanceId)
+ public Watchdog(IByond byond, IChat chat, IDreamDaemonExecutor dreamDaemonExecutor, IInterop interop, IDmbFactory dmbFactory, IEventConsumer eventConsumer, IInstanceManager instanceManager, long instanceId)
{
this.byond = byond ?? throw new ArgumentNullException(nameof(byond));
this.chat = chat ?? throw new ArgumentNullException(nameof(chat));
this.dreamDaemonExecutor = dreamDaemonExecutor ?? throw new ArgumentNullException(nameof(dreamDaemonExecutor));
this.interop = interop ?? throw new ArgumentNullException(nameof(interop));
this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory));
- this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
@@ -154,12 +147,11 @@ namespace Tgstation.Server.Host.Components
HostPath = Application.HostingPath,
InstanceId = instanceId,
NextPort = isPrimary ? launchParameters.SecondaryPort : launchParameters.PrimaryPort,
+ //this line feels hacky, change it and remove the instanceManager dep?
InstanceName = instanceManager.GetInstance(new Host.Models.Instance { Id = instanceId }).GetMetadata().Name,
};
-
- var ddTask = byond.UseExecutable(dreamDaemonPath => RunServer(launchParameters, onSuccessfulStartup, interopInfo, dreamDaemonPath, ddToken), false, true);
- interop.SetRun(isPrimary ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, accessToken, isPrimary);
- return ddTask;
+
+ return byond.UseExecutables((dreamMakerPath, dreamDaemonPath) => RunServer(launchParameters, onSuccessfulStartup, interopInfo, dreamDaemonPath, ddToken), false);
}
catch
{
@@ -191,6 +183,19 @@ namespace Tgstation.Server.Host.Components
return false;
}
+ ///
+ /// Handle
+ ///
+ /// The
+ /// The for the operation
+ /// A representing the running operation
+ Task HandleChatMessage(ChatMessageEventArgs e, CancellationToken cancellationToken)
+ {
+ if (e == null)
+ throw new ArgumentNullException(nameof(e));
+ return chat.SendMessage(e.ChatResponse.Message, e.ChatResponse.ChannelIds, cancellationToken);
+ }
+
///
/// Main loop
///
@@ -203,134 +208,140 @@ namespace Tgstation.Server.Host.Components
if (await byond.GetVersion(cancellationToken).ConfigureAwait(false) == null)
throw new InvalidOperationException("No byond version installed!");
await byond.ClearCache(cancellationToken).ConfigureAwait(false);
- var accessToken = cryptographySuite.GetSecureString();
+ var initialLaunchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false);
var retries = 0;
do
{
var retryDelay = (int)Math.Min(Math.Pow(2, retries), TimeSpan.FromHours(1).Milliseconds); //max of one hour
await Task.Delay(retryDelay, cancellationToken).ConfigureAwait(false);
- //load the event tcs' and get the initial launch parameters
- var primaryPrimedTcs = new TaskCompletionSource();
- interop.OnServerPrimed(() => primaryPrimedTcs.SetResult(null));
- var initialLaunchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false);
- //start the primary server
- var ddPrimaryTask = StartServer(initialLaunchParameters, onSuccessfulStartup, accessToken, true, cancellationToken, out CancellationTokenSource primaryCts);
- try
+ using (var control = interop.CreateRun(initialLaunchParameters.PrimaryPort, initialLaunchParameters.SecondaryPort, HandleChatMessage))
{
- //wait to make sure we got this far
- await onSuccessfulStartup.Task.ConfigureAwait(false);
- onSuccessfulStartup = null;
-
- //wait for either the server to exit or be primed
- await Task.WhenAny(ddPrimaryTask, primaryPrimedTcs.Task).ConfigureAwait(false);
-
- if (ddPrimaryTask.IsCompleted)
+ var primaryPrimedTcs = new TaskCompletionSource();
+ control.OnServerControl += (sender, e) =>
{
- if (await HandleServerCrashed(ddPrimaryTask, true, cancellationToken).ConfigureAwait(false))
- return;
- ++retries;
- continue;
- }
+ if (e.FromPrimaryServer && e.EventType == ServerControlEventType.ServerPrimed)
+ primaryPrimedTcs.TrySetResult(null);
+ };
- var launchParameters = initialLaunchParameters;
- Task ddSecondaryTask = null;
- CancellationTokenSource secondaryCts = null;
+ //start the primary server
+ var ddPrimaryTask = StartServer(initialLaunchParameters, onSuccessfulStartup, control.PrimaryAccessToken, true, cancellationToken, out CancellationTokenSource primaryCts);
try
{
- do
+ //wait to make sure we got this far
+ await onSuccessfulStartup.Task.ConfigureAwait(false);
+ onSuccessfulStartup = null;
+
+ //wait for either the server to exit or be primed
+ await Task.WhenAny(ddPrimaryTask, primaryPrimedTcs.Task).ConfigureAwait(false);
+
+ if (ddPrimaryTask.IsCompleted)
{
- if (ddSecondaryTask == null)
- //start the secondary server
- ddSecondaryTask = StartServer(initialLaunchParameters, null, accessToken, false, cancellationToken, out secondaryCts);
+ if (await HandleServerCrashed(ddPrimaryTask, true, cancellationToken).ConfigureAwait(false))
+ return;
+ ++retries;
+ continue;
+ }
- var newDmbTask = dmbFactory.OnNewerDmb();
-
- //now we wait for something to happen
- await Task.WhenAny(ddSecondaryTask, ddPrimaryTask, newDmbTask).ConfigureAwait(false);
-
- //some helpers
- void PrimaryRestart()
+ var launchParameters = initialLaunchParameters;
+ Task ddSecondaryTask = null;
+ CancellationTokenSource secondaryCts = null;
+ try
+ {
+ do
{
- primaryCts.Dispose();
- ddPrimaryTask = StartServer(initialLaunchParameters, null, accessToken, true, cancellationToken, out primaryCts);
- }
- void SecondaryRestart()
- {
- ddSecondaryTask = null;
- secondaryCts.Dispose();
- };
- Task PrimaryCrash() => HandleServerCrashed(ddPrimaryTask, interop.SecondaryIsOther, cancellationToken);
- Task SecondaryCrash() => HandleServerCrashed(ddSecondaryTask, !interop.SecondaryIsOther, cancellationToken);
+ if (ddSecondaryTask == null)
+ //start the secondary server
+ ddSecondaryTask = StartServer(initialLaunchParameters, null, control.SecondaryAccessToken, false, cancellationToken, out secondaryCts);
- //update available
- if (newDmbTask.IsCompleted)
- {
- //restart the other server but don't treat it as an error
- launchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false);
- //restart other server
- if (interop.SecondaryIsOther)
+ var newDmbTask = dmbFactory.OnNewerDmb();
+
+ //now we wait for something to happen
+ await Task.WhenAny(ddSecondaryTask, ddPrimaryTask, newDmbTask).ConfigureAwait(false);
+
+ //some helpers
+ void PrimaryRestart()
{
- secondaryCts.Cancel();
- await ddSecondaryTask.ConfigureAwait(false);
- cancellationToken.ThrowIfCancellationRequested();
+ primaryCts.Dispose();
+ ddPrimaryTask = StartServer(initialLaunchParameters, null, control.PrimaryAccessToken, true, cancellationToken, out primaryCts);
+ }
+ void SecondaryRestart()
+ {
+ ddSecondaryTask = null;
+ secondaryCts.Dispose();
+ };
+ Task PrimaryCrash() => HandleServerCrashed(ddPrimaryTask, control.SecondaryIsOther, cancellationToken);
+ Task SecondaryCrash() => HandleServerCrashed(ddSecondaryTask, !control.SecondaryIsOther, cancellationToken);
+
+ //update available
+ if (newDmbTask.IsCompleted)
+ {
+ //restart the other server but don't treat it as an error
+ launchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false);
+ //restart other server
+ if (control.SecondaryIsOther)
+ {
+ secondaryCts.Cancel();
+ await ddSecondaryTask.ConfigureAwait(false);
+ cancellationToken.ThrowIfCancellationRequested();
+ SecondaryRestart();
+ }
+ else
+ {
+ primaryCts.Cancel();
+ await ddPrimaryTask.ConfigureAwait(false);
+ cancellationToken.ThrowIfCancellationRequested();
+ PrimaryRestart();
+ }
+ continue;
+ }
+
+ //crash of both servers
+ if (ddSecondaryTask.IsCompleted && ddPrimaryTask.IsCompleted)
+ {
+ //catastrophic, start over
+ var t1 = PrimaryCrash();
+ await Task.WhenAll(t1, SecondaryCrash()).ConfigureAwait(false);
+ if (t1.Result)
+ return;
+ ++retries;
+ continue;
+ }
+
+ //below this point: crash of single server
+
+ //activate the other server and load new launch params
+ var otherServerActivation = control.ActivateOtherServer(cancellationToken);
+ launchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false);
+ await otherServerActivation.ConfigureAwait(false);
+
+ //crash of secondary server
+ if (ddSecondaryTask.IsCompleted)
+ {
+ if (await SecondaryCrash().ConfigureAwait(false))
+ return;
SecondaryRestart();
}
+ //crash of primary server
else
{
- primaryCts.Cancel();
- await ddPrimaryTask.ConfigureAwait(false);
- cancellationToken.ThrowIfCancellationRequested();
+ if (await PrimaryCrash().ConfigureAwait(false))
+ return;
PrimaryRestart();
}
- continue;
- }
-
- //crash of both servers
- if (ddSecondaryTask.IsCompleted && ddPrimaryTask.IsCompleted)
- {
- //catastrophic, start over
- var t1 = PrimaryCrash();
- await Task.WhenAll(t1, SecondaryCrash()).ConfigureAwait(false);
- if (t1.Result)
- return;
- ++retries;
- continue;
- }
-
- //below this point: crash of single server
-
- //activate the other server and load new launch params
- var otherServerActivation = interop.ActivateOtherServer(cancellationToken);
- launchParameters = await launchParametersFactory.GetLaunchParameters(cancellationToken).ConfigureAwait(false);
- await otherServerActivation.ConfigureAwait(false);
-
- //crash of secondary server
- if (ddSecondaryTask.IsCompleted)
- {
- if (await SecondaryCrash().ConfigureAwait(false))
- return;
- SecondaryRestart();
- }
- //crash of primary server
- else
- {
- if (await PrimaryCrash().ConfigureAwait(false))
- return;
- PrimaryRestart();
- }
- } while (true);
+ } while (true);
+ }
+ finally
+ {
+ secondaryCts?.Dispose();
+ }
}
finally
{
- secondaryCts?.Dispose();
+ primaryCts.Dispose();
}
}
- finally
- {
- primaryCts.Dispose();
- }
} while (true);
}