tgstation-server 6.19.2
The /tg/station 13 server suite
Loading...
Searching...
No Matches
SessionController.cs
Go to the documentation of this file.
1using System;
4using System.Linq;
5using System.Text;
8
11
12using Newtonsoft.Json;
13
14using Serilog.Context;
15
32
34{
35#pragma warning disable CA1506
38#pragma warning restore CA1506
39 {
43 internal static bool LogTopicRequests { get; set; } = true;
44
47
50 {
51 get
52 {
53 if (!Lifetime.IsCompleted)
54 throw new InvalidOperationException("ApiValidated cannot be checked while Lifetime is incomplete!");
56 }
57 }
58
61
64
67
69 public Version? DMApiVersion { get; private set; }
70
73
76
78 public Task<int?> Lifetime { get; }
79
81 public Task OnStartup => startupTcs.Task;
82
84 public Task OnReboot => rebootTcs.Task;
85
87 public long? StartupBridgeRequestsReceived { get; private set; }
88
90 public Task RebootGate
91 {
92 get => rebootGate;
93 set
94 {
96 async Task Wrap()
97 {
98 var toAwait = await tcs.Task;
100 await value;
101 }
102
103 tcs.SetResult(Interlocked.Exchange(ref rebootGate, Wrap()));
104 }
105 }
106
108 public Task OnPrime => primeTcs.Task;
109
112
115
118 ? ".net.dmp"
119 : ".dmp";
120
125
130
133
135 public DateTimeOffset? LaunchTime => process.LaunchTime;
136
141
146
151
156
161
166
171
176
181
186
191
195 readonly TaskCompletionSource initialBridgeRequestTcs;
196
201
206
211
216
220 volatile TaskCompletionSource startupTcs;
221
225 volatile TaskCompletionSource rebootTcs;
226
230 volatile TaskCompletionSource primeTcs;
231
235 volatile Task rebootGate;
236
241
246
251
256
261
266
271
296 Api.Models.Instance metadata,
299 Byond.TopicSender.ITopicClient byondTopicSender,
301 IBridgeRegistrar bridgeRegistrar,
303 IAssemblyInformationProvider assemblyInformationProvider,
311 uint? startupTimeout,
312 bool reattached,
313 bool apiValidate)
314 : base(logger)
315 {
322 ArgumentNullException.ThrowIfNull(bridgeRegistrar);
323
325 ArgumentNullException.ThrowIfNull(assemblyInformationProvider);
326
332
334
335 disposed = false;
337 released = false;
338
339 startupTcs = new TaskCompletionSource();
340 rebootTcs = new TaskCompletionSource();
341 primeTcs = new TaskCompletionSource();
342
343 rebootGate = Task.CompletedTask;
344 customEventProcessingTask = Task.CompletedTask;
345
346 // Run this asynchronously because we want to try to avoid any effects sending topics to the server while the initial bridge request is processing
347 // It MAY be the source of a DD crash. See this gist https://gist.github.com/Cyberboss/7776bbeff3a957d76affe0eae95c9f14
348 // Worth further investigation as to if that sequence of events is a reliable crash vector and opening a BYOND bug if it is
349 initialBridgeRequestTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
351
354
355 if (DMApiAvailable)
356 {
358 }
359
361 {
362 bridgeRegistration = bridgeRegistrar.RegisterHandler(this);
363 this.chatTrackingContext.SetChannelSink(this);
364 }
365 else
366 logger.LogTrace(
367 "Not registering session with {reasonWhyDmApiIsBad} DMAPI version for interop!",
368 reattachInformation.Dmb.CompileJob.DMApiVersion == null
369 ? "no"
370 : $"incompatible ({reattachInformation.Dmb.CompileJob.DMApiVersion})");
371
373 {
376 if (postValidationShutdownTask != null)
378
379 return exitCode;
380 }
381
383
385 assemblyInformationProvider,
390
391 logger.LogDebug(
392 "Created session controller. CommsKey: {accessIdentifier}, Port: {port}",
393 reattachInformation.AccessIdentifier,
395 }
396
399 {
401 {
402 if (disposed)
403 return;
404 disposed = true;
405 }
406
407 Logger.LogTrace("Disposing...");
408
409 sessionDurationCts.Cancel();
410 var cancellationToken = CancellationToken.None; // DCT: None available
411 var semaphoreLockTask = TopicSendSemaphore.Lock(cancellationToken);
412
413 if (!released)
414 {
416 Logger,
417 process,
420 cancellationToken);
421 }
422
423 await process.DisposeAsync();
424 engineLock.Dispose();
425 bridgeRegistration?.Dispose();
428 if (initialDmb != null)
429 await initialDmb.DisposeAsync();
430
432
433 chatTrackingContext.Dispose();
434 sessionDurationCts.Dispose();
435
436 if (!released)
437 await Lifetime; // finish the async callback
438
439 (await semaphoreLockTask).Dispose();
441
443 }
444
447 {
449
451 {
453 {
454 Logger.LogWarning("Ignoring bridge request from session without confirmed DMAPI!");
455 return null;
456 }
457
458 Logger.LogTrace("Handling bridge request...");
459
460 try
461 {
462 return await ProcessBridgeCommand(parameters, cancellationToken);
463 }
464 finally
465 {
466 initialBridgeRequestTcs.TrySetResult();
467 }
468 }
469 }
470
482
485 => SendCommand(parameters, false, cancellationToken);
486
489 {
491 return true;
492
493 Logger.LogTrace("Changing reboot state to {newRebootState}", newRebootState);
494
498 cancellationToken);
499
500 return result != null && result.ErrorMessage == null;
501 }
502
504 public void ResetRebootState()
505 {
507 Logger.LogTrace("Resetting reboot state...");
509 }
510
513
516
519
522 {
524 ReattachInformation.Dmb = dmbProvider ?? throw new ArgumentNullException(nameof(dmbProvider));
525 return oldDmb;
526 }
527
539
543 new TopicParameters(
545 cancellationToken);
546
548 public ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken)
549 {
551 return dotnetDumpService.Dump(process, outputFile, minidump, cancellationToken);
552
553 return process.CreateDump(outputFile, minidump, cancellationToken);
554 }
555
559
570 IAssemblyInformationProvider assemblyInformationProvider,
572 uint? startupTimeout,
573 bool reattached,
574 bool apiValidate)
575 {
576 var startTime = DateTimeOffset.UtcNow;
581 var toAwait = Task.WhenAny(startupTask, process.Lifetime);
582
583 if (startupTimeout.HasValue)
584 toAwait = Task.WhenAny(
585 toAwait,
587 TimeSpan.FromSeconds(startupTimeout.Value),
589 .AsTask()); // DCT: None available, task will clean up after delay
590
591 Logger.LogTrace(
592 "Waiting for LaunchResult based on {launchResultCompletionCause}{possibleTimeout}...",
593 useBridgeRequestForLaunchResult ? "initial bridge request" : "process startup",
594 startupTimeout.HasValue ? $" with a timeout of {startupTimeout.Value}s" : String.Empty);
595
597
599 {
602 };
603
604 Logger.LogTrace("Launch result: {launchResult}", result);
605
606 if (!result.ExitCode.HasValue && reattached && !disposed)
607 {
609 new TopicParameters(
610 assemblyInformationProvider.Version,
612 true,
613 sessionDurationCts.Token);
614
615 if (reattachResponse != null)
616 {
617 if (reattachResponse?.CustomCommands != null)
619 else if (reattachResponse != null)
620 Logger.Log(
621 CompileJob.DMApiVersion >= new Version(5, 2, 0)
622 ? LogLevel.Warning
623 : LogLevel.Debug,
624 "DMAPI Interop v{interopVersion} isn't returning the TGS custom commands list. Functionality added in v5.2.0.",
625 CompileJob.DMApiVersion!.Semver());
626 }
627 }
628
629 return result;
630 }
631
636
643 {
644 Logger.LogTrace("Entered post validation terminate task.");
645 if (!await proceedTask)
646 {
647 Logger.LogTrace("Not running post validation terminate task for repeated bridge request.");
648 return;
649 }
650
651 const int GracePeriodSeconds = 30;
652 Logger.LogDebug("Server will terminated in {gracePeriodSeconds}s if it does not exit...", GracePeriodSeconds);
653 var delayTask = asyncDelayer.Delay(TimeSpan.FromSeconds(GracePeriodSeconds), CancellationToken.None).AsTask(); // DCT: None available
654 await Task.WhenAny(process.Lifetime, delayTask);
655
656 if (!process.Lifetime.IsCompleted)
657 {
658 Logger.LogWarning("DMAPI took too long to shutdown server after validation request!");
660 apiValidationStatus = ApiValidationStatus.BadValidationRequest;
661 }
662 else
663 Logger.LogTrace("Server exited properly post validation.");
664 }
665
672#pragma warning disable CA1502 // TODO: Decomplexify
673#pragma warning disable CA1506
675 {
677 switch (parameters.CommandType)
678 {
679 case BridgeCommandType.ChatSend:
680 if (parameters.ChatMessage == null)
681 return BridgeError("Missing chatMessage field!");
682
683 if (parameters.ChatMessage.ChannelIds == null)
684 return BridgeError("Missing channelIds field in chatMessage!");
685
686 if (parameters.ChatMessage.ChannelIds.Any(channelIdString => !UInt64.TryParse(channelIdString, out var _)))
687 return BridgeError("Invalid channelIds in chatMessage!");
688
689 if (parameters.ChatMessage.Text == null)
690 return BridgeError("Missing message field in chatMessage!");
691
692 var anyFailed = false;
693 var parsedChannels = parameters.ChatMessage.ChannelIds.Select(
695 {
697 return channelId;
698 });
699
700 if (anyFailed)
701 return BridgeError("Failed to parse channelIds as U64!");
702
704 parameters.ChatMessage,
706 break;
707 case BridgeCommandType.Prime:
708 Interlocked.Exchange(ref primeTcs, new TaskCompletionSource()).SetResult();
709 break;
710 case BridgeCommandType.Kill:
711 Logger.LogInformation("Bridge requested process termination!");
715 break;
716 case BridgeCommandType.DeprecatedPortUpdate:
717 return BridgeError("Port switching is no longer supported!");
718 case BridgeCommandType.Startup:
719 apiValidationStatus = ApiValidationStatus.BadValidationRequest;
721
723 {
724 var proceedTcs = new TaskCompletionSource<bool>();
727
729 return BridgeError("Startup bridge request was repeated!");
730 }
731
732 if (parameters.Version == null)
733 return BridgeError("Missing dmApiVersion field!");
734
735 DMApiVersion = parameters.Version;
736
737 // TODO: When OD figures out how to unite port and topic_port, set an upper version bound on OD for this check
739 || (EngineVersion.Engine == EngineType.OpenDream && DMApiVersion < new Version(5, 7)))
740 {
742 return BridgeError("Incompatible dmApiVersion!");
743 }
744
745 switch (parameters.MinimumSecurityLevel)
746 {
747 case DreamDaemonSecurity.Ultrasafe:
748 apiValidationStatus = ApiValidationStatus.RequiresUltrasafe;
749 break;
750 case DreamDaemonSecurity.Safe:
752 break;
753 case DreamDaemonSecurity.Trusted:
755 break;
756 case null:
757 return BridgeError("Missing minimumSecurityLevel field!");
758 default:
759 return BridgeError("Invalid minimumSecurityLevel!");
760 }
761
762 Logger.LogTrace("ApiValidationStatus set to {apiValidationStatus}", apiValidationStatus);
763
764 // we create new runtime info here because of potential .Dmb changes (i think. i forget...)
774
775 if (parameters.TopicPort.HasValue)
776 {
777 var newTopicPort = parameters.TopicPort.Value;
778 Logger.LogInformation("Server is requesting use of port {topicPort} for topic communications", newTopicPort);
780 }
781
782 // Load custom commands
785 Interlocked.Exchange(ref startupTcs, new TaskCompletionSource()).SetResult();
786 break;
787 case BridgeCommandType.Reboot:
789 try
790 {
792 var rebootGate = RebootGate; // Important to read this before setting the TCS or it could change
793 Interlocked.Exchange(ref rebootTcs, new TaskCompletionSource()).SetResult();
794 await rebootGate.WaitAsync(cancellationToken);
795 }
796 finally
797 {
799 }
800
801 break;
802 case BridgeCommandType.Chunk:
804 case BridgeCommandType.Event:
805 return TriggerCustomEvent(parameters.EventInvocation);
806 case BridgeCommandType.Deploy:
807 var job = Job.Create(JobCode.AutomaticDeployment, null, metadata, DreamMakerRights.CancelCompile);
809 job,
810 (core, databaseContextFactory, job, progressReporter, jobCancellationToken) =>
812 job,
813 databaseContextFactory,
816 cancellationToken);
817
818 break;
819 case null:
820 return BridgeError("Missing commandType!");
821 default:
822 return BridgeError($"commandType {parameters.CommandType} not supported!");
823 }
824
825 return response;
826 }
827#pragma warning restore CA1506
828#pragma warning restore CA1502
829
836 {
837 Logger.LogWarning("Bridge request error: {message}", message);
838 return new BridgeResponse
839 {
840 ErrorMessage = message,
841 };
842 }
843
851 {
853
855 if (LogTopicRequests)
856 Logger.LogTrace("Topic request: {json}", json);
858 var topicPriority = parameters.IsPriority;
860 return await SendRawTopic(fullCommandString, topicPriority, cancellationToken);
861
862 var interopChunkingVersion = new Version(5, 6, 0);
864 {
865 Logger.LogWarning(
866 "Cannot send topic request as it is exceeds the single request limit of {limitBytes}B ({actualBytes}B) and requires chunking and the current compile job's interop version must be at least {chunkingVersionRequired}!",
870 return null;
871 }
872
874
875 // AccessIdentifer is just noise in a chunked request
878
879 // yes, this straight up ignores unicode, precalculating it is useless when we don't
880 // even know if the UTF8 bytes of the url encoded chunk will fit the window until we do said encoding
881 var fullPayloadSize = (uint)json.Length;
882
884 for (var chunkCount = 2; chunkQueryStrings == null; ++chunkCount)
885 {
889 continue;
890
892 for (var i = 0U; i < chunkCount; ++i)
893 {
897 i == chunkCount - 1
900 var chunkPayload = json.Substring((int)startIndex, (int)subStringLength);
901
902 var chunk = new ChunkData
903 {
904 Payload = chunkPayload,
905 PayloadId = payloadId,
906 SequenceId = i,
907 TotalChunks = (uint)chunkCount,
908 };
909
911 {
912 AccessIdentifier = ReattachInformation.AccessIdentifier,
913 };
914
917 {
918 // too long when encoded, need more chunks
919 chunkQueryStrings = null;
920 break;
921 }
922
924 }
925 }
926
927 Logger.LogTrace("Chunking topic request ({totalChunks} total)...", chunkQueryStrings.Count);
928
931 {
932 if (combinedResponse?.InteropResponse == null || combinedResponse.InteropResponse.ErrorMessage != null)
933 {
934 Logger.LogWarning(
935 "Topic request {chunkingStatus} failed!{potentialRequestError}",
936 possiblyFromCompletedRequest ? "final chunk" : "chunking",
937 combinedResponse?.InteropResponse?.ErrorMessage != null
938 ? $" Request error: {combinedResponse.InteropResponse.ErrorMessage}"
939 : String.Empty);
940 return true;
941 }
942
943 return false;
944 }
945
947 {
950 return null;
951 }
952
953 while ((combinedResponse?.InteropResponse?.MissingChunks?.Count ?? 0) > 0)
954 {
955 Logger.LogWarning("DD is still missing some chunks of topic request P{payloadId}! Sending missing chunks...", payloadId);
956 var missingChunks = combinedResponse!.InteropResponse!.MissingChunks!;
957 var lastIndex = missingChunks.Last();
959 {
963 return null;
964 }
965 }
966
967 return combinedResponse;
968 }
969
977 {
979 var commandString = String.Format(
980 CultureInfo.InvariantCulture,
981 "?{0}={1}",
983 byondTopicSender.SanitizeString(json));
984 return commandString;
985 }
986
995 {
996 if (disposed)
997 {
998 Logger.LogWarning(
999 "Attempted to send a topic on a disposed SessionController");
1000 return null;
1001 }
1002
1004 Byond.TopicSender.TopicResponse? byondResponse;
1005 using (await TopicSendSemaphore.Lock(cancellationToken))
1006 byondResponse = await byondTopicSender.SendWithOptionalPriority(
1008 LogTopicRequests
1009 ? Logger
1010 : NullLogger.Instance,
1012 targetPort,
1013 priority,
1014 cancellationToken);
1015
1016 if (byondResponse == null)
1017 {
1018 if (priority)
1019 Logger.LogError(
1020 "Unable to send priority topic \"{queryString}\"!",
1021 queryString);
1022
1023 return null;
1024 }
1025
1026 var topicReturn = byondResponse.StringData;
1027
1029 if (topicReturn != null)
1030 try
1031 {
1033 }
1034 catch (Exception ex)
1035 {
1036 Logger.LogWarning(ex, "Invalid interop response: {topicReturnString}", topicReturn);
1037 }
1038
1040 }
1041
1050 {
1051 ArgumentNullException.ThrowIfNull(parameters);
1052
1053 if (Lifetime.IsCompleted || disposed)
1054 {
1055 Logger.LogWarning(
1056 "Attempted to send a command to an inactive SessionController: {commandType}",
1057 parameters.CommandType);
1058 return null;
1059 }
1060
1061 if (!DMApiAvailable)
1062 {
1063 Logger.LogTrace("Not sending topic request {commandType} to server without/with incompatible DMAPI!", parameters.CommandType);
1064 return null;
1065 }
1066
1068 if (!bypassLaunchResult)
1069 {
1070 var launchResult = await LaunchResult.WaitAsync(cancellationToken);
1071 if (launchResult.ExitCode.HasValue)
1072 {
1073 Logger.LogDebug("Not sending topic request {commandType} to server that failed to launch!", parameters.CommandType);
1074 return null;
1075 }
1076 }
1077
1078 // meh, this is kind of a hack, but it works
1080 {
1081 Logger.LogDebug("Not sending topic request {commandType} to server that is rebooting/starting.", parameters.CommandType);
1082 return null;
1083 }
1084
1085 using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
1088 {
1089 try
1090 {
1091 var completed = await Task.WhenAny(Lifetime, reboot).WaitAsync(combinedCancellationToken);
1092
1093 Logger.LogDebug(
1094 "Server {action}, cancelling pending command: {commandType}",
1095 completed != reboot
1096 ? "process ended"
1097 : "rebooting",
1098 parameters.CommandType);
1099 cts.Cancel();
1100 }
1102 {
1103 // expected, not even worth tracing
1104 }
1105 catch (Exception ex)
1106 {
1107 Logger.LogError(ex, "Error in CancelIfLifetimeElapses!");
1108 }
1109 }
1110
1113 try
1114 {
1116
1117 void LogCombinedResponse()
1118 {
1119 if (LogTopicRequests && combinedResponse != null)
1120 Logger.LogTrace("Topic response: {topicString}", combinedResponse.ByondTopicResponse.StringData ?? "(NO STRING DATA)");
1121 }
1122
1124
1125 if (combinedResponse?.InteropResponse?.Chunk != null)
1126 {
1127 Logger.LogTrace("Topic response is chunked...");
1128
1129 ChunkData? nextChunk = combinedResponse.InteropResponse.Chunk;
1130 do
1131 {
1133 (completedResponse, _) =>
1134 {
1136 return ValueTask.FromResult<ChunkedTopicParameters?>(null);
1137 },
1138 error =>
1139 {
1140 Logger.LogWarning("Topic response chunking error: {message}", error);
1141 return null;
1142 },
1143 combinedResponse?.InteropResponse?.Chunk,
1145
1146 if (nextRequest != null)
1147 {
1151 nextChunk = combinedResponse?.InteropResponse?.Chunk;
1152 }
1153 else
1154 nextChunk = null;
1155 }
1156 while (nextChunk != null);
1157 }
1158 else
1159 fullResponse = combinedResponse?.InteropResponse;
1160 }
1162 {
1163 Logger.LogDebug(
1164 ex,
1165 "Topic request {cancellationType}!",
1166 combinedCancellationToken.IsCancellationRequested
1167 ? cancellationToken.IsCancellationRequested
1168 ? "cancelled"
1169 : "aborted"
1170 : "timed out");
1171
1172 // throw only if the original token was the trigger
1173 cancellationToken.ThrowIfCancellationRequested();
1174 }
1175 finally
1176 {
1177 cts.Cancel();
1179 }
1180
1181 if (fullResponse?.ErrorMessage != null)
1182 Logger.LogWarning(
1183 "Errored topic response for command {commandType}: {errorMessage}",
1184 parameters.CommandType,
1185 fullResponse.ErrorMessage);
1186
1187 return fullResponse;
1188 }
1189
1196 {
1197 if (invocation == null)
1198 return BridgeError("Missing eventInvocation!");
1199
1200 var eventName = invocation.EventName;
1201 if (eventName == null)
1202 return BridgeError("Missing eventName!");
1203
1204 var notifyCompletion = invocation.NotifyCompletion;
1205 if (!notifyCompletion.HasValue)
1206 return BridgeError("Missing notifyCompletion!");
1207
1209 {
1211 };
1212
1213 eventParams.AddRange(invocation
1214 .Parameters?
1215 .Where(param => param != null)
1216 .Cast<string>()
1217 ?? Enumerable.Empty<string>());
1218
1219 var eventId = Guid.NewGuid();
1220 Logger.LogInformation("Triggering custom event \"{eventName}\": {eventId}", eventName, eventId);
1221
1222 var cancellationToken = sessionDurationCts.Token;
1224
1225 async Task ProcessEvent()
1226 {
1227 try
1228 {
1230 try
1231 {
1232 await eventTask.Value;
1233 exception = null;
1234 }
1235 catch (Exception ex)
1236 {
1237 exception = ex;
1238 }
1239
1240 if (notifyCompletion.Value)
1243 cancellationToken);
1244 else if (exception == null)
1245 Logger.LogTrace("Finished custom event {eventId}, not sending notification.", eventId);
1246
1247 if (exception != null)
1248 throw exception;
1249 }
1251 {
1252 Logger.LogDebug(ex, "Custom event invocation {eventId} aborted!", eventId);
1253 }
1254 catch (Exception ex)
1255 {
1256 Logger.LogWarning(ex, "Custom event invocation {eventId} errored!", eventId);
1257 }
1258 }
1259
1260 if (!eventTask.HasValue)
1261 return BridgeError("Event refused to execute due to matching a TGS event!");
1262
1264 {
1268 }
1269
1270 return new BridgeResponse
1271 {
1272 EventId = notifyCompletion.Value
1273 ? eventId.ToString()
1274 : null,
1275 };
1276 }
1277 }
1278}
Information about an engine installation.
Metadata about a server instance.
Definition Instance.cs:9
Representation of the initial data passed as part of a BridgeCommandType.Startup request.
Version ServerVersion
The IAssemblyInformationProvider.Version.
DreamDaemonVisibility Visibility
The DreamDaemonSecurity level of the launch.
string InstanceName
The NamedEntity.Name of the owner at the time of launch.
bool ApiValidateOnly
If DD should just respond if it's API is working and then exit.
DreamDaemonSecurity SecurityLevel
The DreamDaemonSecurity level of the launch.
Represents an update of ChannelRepresentations.
Definition ChatUpdate.cs:13
A packet of a split serialized set of data.
Definition ChunkData.cs:7
uint? PayloadId
The ID of the full request to differentiate different chunkings.Nullable to prevent default value omi...
Class that deserializes chunked interop payloads.
Definition Chunker.cs:16
ILogger< Chunker > Logger
The ILogger for the Chunker.
Definition Chunker.cs:20
uint NextPayloadId
Gets a payload ID for use in a new ChunkSetInfo.
Definition Chunker.cs:26
Constants used for communication with the DMAPI.
static readonly JsonSerializerSettings SerializerSettings
JsonSerializerSettings for use when communicating with the DMAPI.
const uint MaximumTopicRequestLength
The maximum length in bytes of a Byond.TopicSender.ITopicClient payload.
static readonly Version InteropVersion
The DMAPI InteropVersion being used.
const string TopicData
Parameter json is encoded in for topic requests.
string AccessIdentifier
Used to identify and authenticate the DreamDaemon instance.
static TopicParameters CreateInstanceRenamedTopicParameters(string newInstanceName)
Initializes a new instance of the TopicParameters class.
ChunkData? Chunk
The ChunkData for a partial request.
Combines a Byond.TopicSender.TopicResponse with a TopicResponse.
Represents the result of trying to start a DD process.
Parameters necessary for duplicating a ISessionController session.
RuntimeInformation? RuntimeInformation
The Interop.Bridge.RuntimeInformation for the DMAPI.
IDmbProvider? InitialDmb
The IDmbProvider initially used to launch DreamDaemon. Should be a different IDmbProvider than Dmb....
IDmbProvider Dmb
The IDmbProvider used by DreamDaemon.
async ValueTask InstanceRenamed(string newInstanceName, CancellationToken cancellationToken)
Called when the owning Instance is renamed.A ValueTask representing the running operation.
ValueTask< TopicResponse?> SendCommand(TopicParameters parameters, CancellationToken cancellationToken)
Sends a command to DreamDaemon through /world/Topic().A ValueTask<TResult> resulting in the TopicResp...
async ValueTask< BridgeResponse?> ProcessBridgeCommand(BridgeParameters parameters, CancellationToken cancellationToken)
Handle a set of bridge parameters .
readonly Byond.TopicSender.ITopicClient byondTopicSender
The Byond.TopicSender.ITopicClient for the SessionController.
string DumpFileExtension
The file extension to use for process dumps created from this session.
ApiValidationStatus apiValidationStatus
The ApiValidationStatus for the SessionController.
readonly object synchronizationLock
lock object for port updates and disposed.
DateTimeOffset? LaunchTime
When the process was started.
void AdjustPriority(bool higher)
Set's the owned global::System.Diagnostics.Process.PriorityClass to a non-normal value.
async ValueTask< TopicResponse?> SendCommand(TopicParameters parameters, bool bypassLaunchResult, CancellationToken cancellationToken)
Sends a command to DreamDaemon through /world/Topic().
volatile uint rebootBridgeRequestsProcessing
The number of currently active calls to ProcessBridgeRequest(BridgeParameters, CancellationToken) fro...
readonly IDotnetDumpService dotnetDumpService
The IDotnetDumpService for the SessionController.
readonly Api.Models.Instance metadata
The Instance metadata.
Task OnReboot
A Task that completes when the server calls /world/TgsReboot().
bool DMApiAvailable
If the DMAPI may be used this session.
readonly TaskCompletionSource initialBridgeRequestTcs
The TaskCompletionSource that completes when DD makes it's first bridge request.
bool disposed
If the SessionController has been disposed.
void ResetRebootState()
Changes RebootState to RebootState.Normal without telling the DMAPI.
readonly IEngineExecutableLock engineLock
The IEngineExecutableLock for the SessionController.
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the SessionController.
long? StartupBridgeRequestsReceived
The number of times a startup bridge request has been received. null if DMApiAvailable is false.
async ValueTask< BridgeResponse?> ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
Handle a set of bridge parameters .A ValueTask<TResult> resulting in the BridgeResponse for the reque...
ReattachInformation ReattachInformation
The up to date Session.ReattachInformation.
volatile Task rebootGate
Backing field for RebootGate.
bool TerminationWasIntentional
If the DreamDaemon instance sent a.
readonly IEventConsumer eventConsumer
The IEventConsumer for the SessionController.
BridgeResponse BridgeError(string message)
Log and return a BridgeResponse for a given message .
bool released
If process should be kept alive instead.
bool terminationWasIntentionalForced
Backing field for overriding TerminationWasIntentional.
readonly IChatManager chat
The IChatManager for the SessionController.
double MeasureProcessorTimeDelta()
Gets the estimated CPU usage fraction of the process based on the last time this was called....
readonly IChatTrackingContext chatTrackingContext
The IChatTrackingContext for the SessionController.
Task< int?> Lifetime
The Task<TResult> resulting in the exit code of the process or null if the process was detached.
readonly CancellationTokenSource sessionDurationCts
A CancellationTokenSource used for tasks that should not exceed the lifetime of the session.
ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken)
Create a dump file of the process.A ValueTask representing the running operation.
volatile TaskCompletionSource startupTcs
The TaskCompletionSource that completes when DD sends a valid startup bridge request.
async Task PostValidationShutdown(Task< bool > proceedTask)
Terminates the server after ten seconds if it does not exit.
readonly bool apiValidationSession
If this session is meant to validate the presence of the DMAPI.
FifoSemaphore TopicSendSemaphore
The FifoSemaphore used to prevent concurrent calls into /world/Topic().
async ValueTask UpdateChannels(IEnumerable< ChannelRepresentation > newChannels, CancellationToken cancellationToken)
Called when newChannels are set.A ValueTask representing the running operation.
string GenerateQueryString(TopicParameters parameters, out string json)
Generates a Byond.TopicSender.ITopicClient query string for a given set of parameters .
readonly IDreamMaker dreamMaker
The IDreamMaker for the SessionController.
void CheckDisposed()
Throws an ObjectDisposedException if DisposeAsync has been called.
readonly? IBridgeRegistration bridgeRegistration
The IBridgeRegistration for the SessionController.
long? MemoryUsage
Gets the process' memory usage in bytes.
async Task< LaunchResult > GetLaunchResult(IAssemblyInformationProvider assemblyInformationProvider, IAsyncDelayer asyncDelayer, uint? startupTimeout, bool reattached, bool apiValidate)
The Task<TResult> for LaunchResult.
IAsyncDisposable ReplaceDmbProvider(IDmbProvider dmbProvider)
Replace the IDmbProvider in use with a given newProvider , disposing the old one.An IAsyncDisposable ...
Task OnStartup
A Task that completes when the server calls /world/TgsNew().
bool ProcessingRebootBridgeRequest
If the ISessionController is currently processing a bridge request from TgsReboot().
ValueTask Release()
Releases the IProcess without terminating it. Also calls IDisposable.Dispose.A ValueTask representing...
volatile? Task postValidationShutdownTask
Task for shutting down the server if it is taking too long after validation.
readonly IJobManager jobManager
The IJobManager for the SessionController.
volatile Task customEventProcessingTask
The Task representing calls to TriggerCustomEvent(CustomEventInvocation?).
Task OnPrime
A Task that completes when the server calls /world/TgsInitializationComplete().
async ValueTask< CombinedTopicResponse?> SendTopicRequest(TopicParameters parameters, CancellationToken cancellationToken)
Send a topic request for given parameters to DreamDaemon, chunking it if necessary.
volatile TaskCompletionSource rebootTcs
The TaskCompletionSource that completes when DD tells us about a reboot.
async ValueTask< CombinedTopicResponse?> SendRawTopic(string queryString, bool priority, CancellationToken cancellationToken)
Send a given queryString to DreamDaemon's /world/Topic.
volatile TaskCompletionSource primeTcs
The TaskCompletionSource that completes when DD tells us it's primed.
async ValueTask< bool > SetRebootState(RebootState newRebootState, CancellationToken cancellationToken)
Attempts to change the current RebootState to newRebootState .A ValueTask<TResult> resulting in true ...
Task RebootGate
A Task that must complete before a TgsReboot() bridge request can complete.
readonly IProcess process
The IProcess for the SessionController.
BridgeResponse TriggerCustomEvent(CustomEventInvocation? invocation)
Trigger a custom event from a given invocation .
SessionController(ReattachInformation reattachInformation, Api.Models.Instance metadata, IProcess process, IEngineExecutableLock engineLock, Byond.TopicSender.ITopicClient byondTopicSender, IChatTrackingContext chatTrackingContext, IBridgeRegistrar bridgeRegistrar, IChatManager chat, IAssemblyInformationProvider assemblyInformationProvider, IAsyncDelayer asyncDelayer, IDotnetDumpService dotnetDumpService, IEventConsumer eventConsumer, IJobManager jobManager, IDreamMaker dreamMaker, ILogger< SessionController > logger, Func< ValueTask > postLifetimeCallback, uint? startupTimeout, bool reattached, bool apiValidate)
Initializes a new instance of the SessionController class.
static Job Create(JobCode code, User? startedBy, Api.Models.Instance instance)
Creates a new job for registering in the Jobs.IJobService.
RebootState RebootState
The current DreamDaemon reboot state.
ushort Port
The port the game server was last listening on.
A first-in first-out async semaphore.
async ValueTask< SemaphoreSlimContext > Lock(CancellationToken cancellationToken)
Locks the FifoSemaphore.
Helpers for manipulating the Serilog.Context.LogContext.
const string InstanceIdContextProperty
The Serilog.Context.LogContext property name for Models.Instance Api.Models.EntityId....
Notifyee of when ChannelRepresentations in a IChatTrackingContext are updated.
For managing connected chat services.
void QueueMessage(MessageContent message, IEnumerable< ulong > channelIds)
Queue a chat message to a given set of channelIds .
Represents a tracking of dynamic chat json files.
void SetChannelSink(IChannelSink channelSink)
Sets the channelSink for the IChatTrackingContext.
Provides absolute paths to the latest compiled .dmbs.
void KeepAlive()
Disposing the IDmbProvider won't cause a cleanup of the working directory.
EngineVersion EngineVersion
The Api.Models.EngineVersion used to build the .dmb.
Models.CompileJob CompileJob
The CompileJob of the .dmb.
ValueTask DeploymentProcess(Job job, IDatabaseContextFactory databaseContextFactory, JobProgressReporter progressReporter, CancellationToken cancellationToken)
Create and a compile job and insert it into the database. Meant to be called by a IJobManager.
Represents usage of the two primary BYOND server executables.
void DoNotDeleteThisSession()
Call if, during a detach, this version should not be deleted.
bool UseDotnetDump
If dotnet-dump should be used to create process dumps for this installation.
ValueTask StopServerProcess(ILogger logger, IProcess process, string accessIdentifier, ushort port, CancellationToken cancellationToken)
Kills a given engine server process .
Consumes EventTypes and takes the appropriate actions.
ValueTask? HandleCustomEvent(string eventName, IEnumerable< string?> parameters, CancellationToken cancellationToken)
Handles a given custom event.
IBridgeRegistration RegisterHandler(IBridgeHandler bridgeHandler)
Register a given bridgeHandler .
Handles communication with a DreamDaemon IProcess.
Manages the runtime of Jobs.
ValueTask RegisterOperation(Job job, JobEntrypoint operation, CancellationToken cancellationToken)
Registers a given Job and begins running it.
Service for managing the dotnet-dump installation.
ValueTask Dump(IProcess process, string outputFile, bool minidump, CancellationToken cancellationToken)
Attempt to dump a given process .
void SuspendProcess()
Suspends the process.
void ResumeProcess()
Resumes the process.
void AdjustPriority(bool higher)
Set's the owned global::System.Diagnostics.Process.PriorityClass to a non-normal value.
long? MemoryUsage
Gets the process' memory usage in bytes.
double MeasureProcessorTimeDelta()
Gets the estimated CPU usage fraction of the process based on the last time this was called.
DateTimeOffset? LaunchTime
When the process was started.
ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken)
Create a dump file of the process.
Task< int?> Lifetime
The Task<TResult> resulting in the exit code of the process or null if the process was detached.
Abstraction over a global::System.Diagnostics.Process.
Definition IProcess.cs:11
Task Startup
The Task representing the time until the IProcess becomes "idle".
Definition IProcess.cs:20
void Terminate()
Asycnhronously terminates the process.
ValueTask Delay(TimeSpan timeSpan, CancellationToken cancellationToken)
Create a Task that completes after a given timeSpan .
JobCode
The different types of Response.JobResponse.
Definition JobCode.cs:9
DreamDaemonSecurity
DreamDaemon's security level.
EngineType
The type of engine the codebase is using.
Definition EngineType.cs:7
@ Byond
Build your own net dream.
@ List
User may list files if the Models.Instance allows it.
DreamMakerRights
Rights for deployment.
BridgeCommandType
Represents the BridgeParameters.CommandType.
@ Chunk
DreamDaemon attempting to send a longer bridge message.
RebootState
Represents the action to take when /world/Reboot() is called.
Definition RebootState.cs:7
ApiValidationStatus
Status of DMAPI validation.