Merge pull request #1516 from tgstation/DMAPIHeartbeatEvent [DMDeploy]

DMAPI Health Check Event
This commit is contained in:
Jordan Dominion
2023-06-04 13:11:52 -04:00
committed by GitHub
18 changed files with 143 additions and 113 deletions
+3 -3
View File
@@ -6,9 +6,9 @@
<TgsCoreVersion>5.12.4</TgsCoreVersion>
<TgsConfigVersion>4.6.0</TgsConfigVersion>
<TgsApiVersion>9.10.2</TgsApiVersion>
<TgsApiLibraryVersion>10.4.1</TgsApiLibraryVersion>
<TgsClientVersion>11.4.3</TgsClientVersion>
<TgsDmapiVersion>6.4.5</TgsDmapiVersion>
<TgsApiLibraryVersion>11.0.0</TgsApiLibraryVersion>
<TgsClientVersion>12.0.0</TgsClientVersion>
<TgsDmapiVersion>6.5.0</TgsDmapiVersion>
<TgsInteropVersion>5.6.1</TgsInteropVersion>
<TgsHostWatchdogVersion>1.2.2</TgsHostWatchdogVersion>
<TgsContainerScriptVersion>1.2.1</TgsContainerScriptVersion>
+2 -2
View File
@@ -25,7 +25,7 @@
- Multiple chat bots per instance: Up to 65535 as a matter of fact (who knows why I chose that number?)!
- Automatic Chat Bot Reconnection Intervals: Set in minutes.
- Better Error State Handling: The Server and Watchdog aren't your momma's boys anymore. Every error state will be automatically resolved or reported with recommended actions.
- Watchdog Heartbeats: An interval in seconds can now be set at which TGS will send /world/Topic() packets to DreamDaemon. If four of these are missed, the server will be rebooted. No more endless @Key Holder pings in discord (and I can finally unmute the /tg/ guild)! This feature can be disabled.
- Watchdog Health Checks: An interval in seconds can now be set at which TGS will send /world/Topic() packets to DreamDaemon. If four of these are missed, the server will be rebooted. No more endless @Key Holder pings in discord (and I can finally unmute the /tg/ guild)! This feature can be disabled.
- Better DMAPI: No longer requires injecting a .NET runtime .dll into the DreamDaemon process. DD -> TGS communication is now handled securely via BYOND's native /world/Export() API ("But Cyberboss, BYOND only supports GET requests." Who said anything about respecting HTTP standards when dealing with BYOND?).
- Safe/Ultrasafe Security Support: Thanks to the new DMAPI, the ultrasafe and safe security levels may be used without running into BYOND's limitations. But no one really cares...
- Private/Invisible visibility Support: Stored per instance.
@@ -86,7 +86,7 @@ tgstation-server is a BYOND server managment suite. It includes all the followin
- GitHub Deployment Environments support
- The Watchdog, a DreamDaemon uptime monitor
- Automatically restarts the server when crashed until told to stop
- "Heartbeat" system for checking DreamDaemon isn't hung
- "Health Check" system for checking DreamDaemon isn't hung
- Seamless automatic application of new deployments on /world/Reboot
- Automatic startup at server boot time
- Automatically handles Windows prompts related to trusted mode
+65 -60
View File
@@ -1,6 +1,6 @@
// tgstation-server DMAPI
#define TGS_DMAPI_VERSION "6.4.5"
#define TGS_DMAPI_VERSION "6.5.0"
// All functions and datums outside this document are subject to change with any version and should not be relied on.
@@ -12,8 +12,8 @@
// Comment this out once you've filled in the below.
#error TGS API unconfigured
// Uncomment this if you wish to allow the game to interact with TGS 3.
// This will raise the minimum required security level of your game to TGS_SECURITY_TRUSTED due to it utilizing call()()
// Uncomment this if you wish to allow the game to interact with TGS 3..
// This will raise the minimum required security level of your game to TGS_SECURITY_TRUSTED due to it utilizing call()().
//#define TGS_V3_API
// Required interfaces (fill in with your codebase equivalent):
@@ -52,44 +52,46 @@
// EVENT CODES
/// Before a reboot mode change, extras parameters are the current and new reboot mode enums
/// Before a reboot mode change, extras parameters are the current and new reboot mode enums.
#define TGS_EVENT_REBOOT_MODE_CHANGE -1
/// Before a port change is about to happen, extra parameters is new port
/// Before a port change is about to happen, extra parameters is new port.
#define TGS_EVENT_PORT_SWAP -2
/// Before the instance is renamed, extra parameter is the new name
/// Before the instance is renamed, extra parameter is the new name.
#define TGS_EVENT_INSTANCE_RENAMED -3
/// After the watchdog reattaches to DD, extra parameter is the new [/datum/tgs_version] of the server
/// After the watchdog reattaches to DD, extra parameter is the new [/datum/tgs_version] of the server.
#define TGS_EVENT_WATCHDOG_REATTACH -4
/// When the watchdog sends a health check to DD. No parameters.
#define TGS_EVENT_HEALTH_CHECK -5
/// When the repository is reset to its origin reference. Parameters: Reference name, Commit SHA
/// When the repository is reset to its origin reference. Parameters: Reference name, Commit SHA.
#define TGS_EVENT_REPO_RESET_ORIGIN 0
/// When the repository performs a checkout. Parameters: Checkout git object
/// When the repository performs a checkout. Parameters: Checkout git object.
#define TGS_EVENT_REPO_CHECKOUT 1
/// When the repository performs a fetch operation. No parameters
/// When the repository performs a fetch operation. No parameters.
#define TGS_EVENT_REPO_FETCH 2
/// When the repository test merges. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user
/// When the repository test merges. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user.
#define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3
/// Before the repository makes a sychronize operation. Parameters: Absolute repostiory path
/// Before the repository makes a sychronize operation. Parameters: Absolute repostiory path.
#define TGS_EVENT_REPO_PRE_SYNCHRONIZE 4
/// Before a BYOND install operation begins. Parameters: [/datum/tgs_version] of the installing BYOND
/// Before a BYOND install operation begins. Parameters: [/datum/tgs_version] of the installing BYOND.
#define TGS_EVENT_BYOND_INSTALL_START 5
/// When a BYOND install operation fails. Parameters: Error message
#define TGS_EVENT_BYOND_INSTALL_FAIL 6
/// When the active BYOND version changes. Parameters: (Nullable) [/datum/tgs_version] of the current BYOND, [/datum/tgs_version] of the new BYOND
/// When the active BYOND version changes. Parameters: (Nullable) [/datum/tgs_version] of the current BYOND, [/datum/tgs_version] of the new BYOND.
#define TGS_EVENT_BYOND_ACTIVE_VERSION_CHANGE 7
/// When the compiler starts running. Parameters: Game directory path, origin commit SHA
/// When the compiler starts running. Parameters: Game directory path, origin commit SHA.
#define TGS_EVENT_COMPILE_START 8
/// When a compile is cancelled. No parameters
/// When a compile is cancelled. No parameters.
#define TGS_EVENT_COMPILE_CANCELLED 9
/// When a compile fails. Parameters: Game directory path, [TRUE]/[FALSE] based on if the cause for failure was DMAPI validation
/// When a compile fails. Parameters: Game directory path, [TRUE]/[FALSE] based on if the cause for failure was DMAPI validation.
#define TGS_EVENT_COMPILE_FAILURE 10
/// When a compile operation completes. Note, this event fires before the new .dmb is loaded into the watchdog. Consider using the [TGS_EVENT_DEPLOYMENT_COMPLETE] instead. Parameters: Game directory path
/// When a compile operation completes. Note, this event fires before the new .dmb is loaded into the watchdog. Consider using the [TGS_EVENT_DEPLOYMENT_COMPLETE] instead. Parameters: Game directory path.
#define TGS_EVENT_COMPILE_COMPLETE 11
/// When an automatic update for the current instance begins. No parameters
/// When an automatic update for the current instance begins. No parameters.
#define TGS_EVENT_INSTANCE_AUTO_UPDATE_START 12
/// When the repository encounters a merge conflict: Parameters: Base SHA, target SHA, base reference, target reference
/// When the repository encounters a merge conflict: Parameters: Base SHA, target SHA, base reference, target reference.
#define TGS_EVENT_REPO_MERGE_CONFLICT 13
/// When a deployment completes. No Parameters
/// When a deployment completes. No Parameters.
#define TGS_EVENT_DEPLOYMENT_COMPLETE 14
/// Before the watchdog shuts down. Not sent for graceful shutdowns. No parameters.
#define TGS_EVENT_WATCHDOG_SHUTDOWN 15
@@ -104,11 +106,11 @@
#define TGS_EVENT_WORLD_PRIME 21
// DMAPI also doesnt implement this
// #define TGS_EVENT_DREAM_DAEMON_LAUNCH 22
/// After a single submodule update is performed. Parameters: Updated submodule name
/// After a single submodule update is performed. Parameters: Updated submodule name.
#define TGS_EVENT_REPO_SUBMODULE_UPDATE 23
/// After CodeModifications are applied, before DreamMaker is run. Parameters: Game directory path, origin commit sha, byond version
/// After CodeModifications are applied, before DreamMaker is run. Parameters: Game directory path, origin commit sha, byond version.
#define TGS_EVENT_PRE_DREAM_MAKER 24
/// Whenever a deployment folder is deleted from disk. Parameters: Game directory path
/// Whenever a deployment folder is deleted from disk. Parameters: Game directory path.
#define TGS_EVENT_DEPLOYMENT_CLEANUP 25
// OTHER ENUMS
@@ -164,28 +166,28 @@
/datum/tgs_revision_information
/// Full SHA of the commit.
var/commit
/// ISO 8601 timestamp of when the commit was created
/// ISO 8601 timestamp of when the commit was created.
var/timestamp
/// Full sha of last known remote commit. This may be null if the TGS repository is not currently tracking a remote branch.
var/origin_commit
/// Represents a version.
/datum/tgs_version
/// The suite/major version number
/// The suite/major version number.
var/suite
// This group of variables can be null to represent a wild card
/// The minor version number. null for wildcards
// This group of variables can be null to represent a wild card.
/// The minor version number. null for wildcards.
var/minor
/// The patch version number. null for wildcards
/// The patch version number. null for wildcards.
var/patch
/// Legacy version number. Generally null
/// Legacy version number. Generally null.
var/deprecated_patch
/// Unparsed string value
/// Unparsed string value.
var/raw_parameter
/// String value minus prefix
/// String value minus prefix.
var/deprefixed_parameter
/**
@@ -231,38 +233,43 @@
var/is_admin_channel
/// [TRUE]/[FALSE] if the channel is a private message channel for a [/datum/tgs_chat_user].
var/is_private_channel
/// Tag string associated with the channel in TGS
/// Tag string associated with the channel in TGS.
var/custom_tag
/// [TRUE]/[FALSE] if the channel supports embeds
/// [TRUE]/[FALSE] if the channel supports embeds.
var/embeds_supported
// Represents a chat user
/datum/tgs_chat_user
/// TGS internal user ID.
var/id
// The user's display name.
/// The user's display name.
var/friendly_name
// The string to use to ping this user in a message.
/// The string to use to ping this user in a message.
var/mention
/// The [/datum/tgs_chat_channel] the user was from
/// The [/datum/tgs_chat_channel] the user was from.
var/datum/tgs_chat_channel/channel
/// User definable handler for TGS events.
/datum/tgs_event_handler
/// If the handler receieves [TGS_EVENT_HEALTH_CHECK] events.
var/receive_health_checks = FALSE
/**
* User definable callback for handling TGS events.
*
* event_code - One of the TGS_EVENT_ defines. Extra parameters will be documented in each
* event_code - One of the TGS_EVENT_ defines. Extra parameters will be documented in each.
*/
/datum/tgs_event_handler/proc/HandleEvent(event_code, ...)
set waitfor = FALSE
return
/// User definable chat command
/// User definable chat command.
/datum/tgs_chat_command
/// The string to trigger this command on a chat bot. e.g `@bot name ...` or `!tgs name ...`
/// The string to trigger this command on a chat bot. e.g `@bot name ...` or `!tgs name ...`.
var/name = ""
/// The help text displayed for this command
/// The help text displayed for this command.
var/help_text = ""
/// If this command should be available to game administrators only
/// If this command should be available to game administrators only.
var/admin_only = FALSE
/// A subtype of [/datum/tgs_chat_command] that is ignored when enumerating available commands. Use this to create shared base /datums for commands.
var/ignore_type
@@ -276,7 +283,7 @@
/datum/tgs_chat_command/proc/Run(datum/tgs_chat_user/sender, params)
CRASH("[type] has no implementation for Run()")
/// User definable chat message
/// User definable chat message.
/datum/tgs_message_content
/// The tring content of the message. Must be provided in New().
var/text
@@ -300,7 +307,7 @@
/// Timestamp must be encoded as: time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss"). Use the active timezone.
var/timestamp
/// Colour must be #AARRGGBB or #RRGGBB hex string
/// Colour must be #AARRGGBB or #RRGGBB hex string.
var/colour
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure for details.
@@ -318,7 +325,7 @@
var/list/datum/tgs_chat_embed/field/fields
/// Common datum for similar discord embed medias
/// Common datum for similar discord embed medias.
/datum/tgs_chat_embed/media
/// Must be set in New().
var/url
@@ -396,16 +403,16 @@
// No function below this succeeds if it TgsAvailable() returns FALSE or if TgsNew() has yet to be called.
/**
* Forces a hard reboot of DreamDaemon by ending the process.
* Forces a hard reboot of DreamDaemon by ending the process. This function may sleep!
*
* Unlike del(world) clients will try to reconnect.
* If TGS has not requested a [TGS_REBOOT_MODE_SHUTDOWN] DreamDaemon will be launched again
* If TGS has not requested a [TGS_REBOOT_MODE_SHUTDOWN] DreamDaemon will be launched again.
*/
/world/proc/TgsEndProcess()
return
/**
* Send a message to connected chats.
* Send a message to connected chats. This function may sleep!
*
* message - The [/datum/tgs_message_content] to send.
* admin_only: If [TRUE], message will be sent to admin connected chats. Vice-versa applies.
@@ -414,7 +421,7 @@
return
/**
* Send a private message to a specific user.
* Send a private message to a specific user. This function may sleep!
*
* message - The [/datum/tgs_message_content] to send.
* user: The [/datum/tgs_chat_user] to PM.
@@ -422,10 +429,8 @@
/world/proc/TgsChatPrivateMessage(datum/tgs_message_content/message, datum/tgs_chat_user/user)
return
// The following functions will sleep if a call to TgsNew() is sleeping
/**
* Send a message to connected chats that are flagged as game-related in TGS.
* Send a message to connected chats that are flagged as game-related in TGS. This function may sleep!
*
* message - The [/datum/tgs_message_content] to send.
* channels - Optional list of [/datum/tgs_chat_channel]s to restrict the message to.
@@ -433,38 +438,38 @@
/world/proc/TgsChatBroadcast(datum/tgs_message_content/message, list/channels = null)
return
/// Returns the current [/datum/tgs_version] of TGS if it is running the server, null otherwise.
/// Returns the current [/datum/tgs_version] of TGS if it is running the server, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
/world/proc/TgsVersion()
return
/// Returns the current [/datum/tgs_version] of the DMAPI being used if it was activated, null otherwise.
/// Returns the current [/datum/tgs_version] of the DMAPI being used if it was activated, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
/world/proc/TgsApiVersion()
return
/// Returns the name of the TGS instance running the game if TGS is present, null otherwise.
/// Returns the name of the TGS instance running the game if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
/world/proc/TgsInstanceName()
return
/// Return the current [/datum/tgs_revision_information] of the running server if TGS is present, null otherwise.
/// Return the current [/datum/tgs_revision_information] of the running server if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
/world/proc/TgsRevision()
return
/// Returns the current BYOND security level as a TGS_SECURITY_ define if TGS is present, null otherwise.
/// Returns the current BYOND security level as a TGS_SECURITY_ define if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
/world/proc/TgsSecurityLevel()
return
/// Returns a list of active [/datum/tgs_revision_information/test_merge]s if TGS is present, null otherwise.
/// Returns a list of active [/datum/tgs_revision_information/test_merge]s if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
/world/proc/TgsTestMerges()
return
/// Returns a list of connected [/datum/tgs_chat_channel]s if TGS is present, null otherwise.
/// Returns a list of connected [/datum/tgs_chat_channel]s if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
/world/proc/TgsChatChannelInfo()
return
/*
The MIT License
Copyright (c) 2017 Jordan Brown
Copyright (c) 2017-2023 Jordan Brown
Permission is hereby granted, free of charge,
to any person obtaining a copy of this software and
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2017 Jordan Brown
Copyright (c) 2017-2023 Jordan Brown
Permission is hereby granted, free of charge,
to any person obtaining a copy of this software and
+1 -1
View File
@@ -75,7 +75,7 @@
#define DMAPI5_TOPIC_COMMAND_INSTANCE_RENAMED 4
#define DMAPI5_TOPIC_COMMAND_CHAT_CHANNELS_UPDATE 5
#define DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE 6
#define DMAPI5_TOPIC_COMMAND_HEARTBEAT 7
#define DMAPI5_TOPIC_COMMAND_HEALTHCHECK 7
#define DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH 8
#define DMAPI5_TOPIC_COMMAND_SEND_CHUNK 9
#define DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK 10
+3 -1
View File
@@ -137,7 +137,9 @@
server_port = new_port
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_HEARTBEAT)
if(DMAPI5_TOPIC_COMMAND_HEALTHCHECK)
if(event_handler?.receive_health_checks)
event_handler.HandleEvent(TGS_EVENT_HEALTH_CHECK)
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH)
+1 -1
View File
@@ -75,7 +75,7 @@
#undef DMAPI5_TOPIC_COMMAND_INSTANCE_RENAMED
#undef DMAPI5_TOPIC_COMMAND_CHAT_CHANNELS_UPDATE
#undef DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE
#undef DMAPI5_TOPIC_COMMAND_HEARTBEAT
#undef DMAPI5_TOPIC_COMMAND_HEALTHCHECK
#undef DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH
#undef DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE
@@ -55,14 +55,14 @@ namespace Tgstation.Server.Api.Models.Internal
public uint? StartupTimeout { get; set; }
/// <summary>
/// The number of seconds between each watchdog heartbeat. 0 disables.
/// The number of seconds between each watchdog health check. 0 disables.
/// </summary>
[Required]
[ResponseOptions]
public uint? HeartbeatSeconds { get; set; }
/// <summary>
/// If a process core dump should be created prior to restarting the watchdog due to heartbeat failure.
/// If a process core dump should be created prior to restarting the watchdog due to health check failure.
/// </summary>
[Required]
[ResponseOptions]
@@ -108,7 +108,7 @@ namespace Tgstation.Server.Api.Models.Internal
&& TopicRequestTimeout == otherParameters.TopicRequestTimeout
&& AdditionalParameters == otherParameters.AdditionalParameters
&& StartProfiler == otherParameters.StartProfiler
&& LogOutput == otherParameters.LogOutput; // We intentionally don't check StartupTimeout, heartbeat seconds, or heartbeat dump as they don't matter in terms of the watchdog
&& LogOutput == otherParameters.LogOutput; // We intentionally don't check StartupTimeout, health check seconds, or health check dump as they don't matter in terms of the watchdog
}
}
}
@@ -76,7 +76,7 @@ namespace Tgstation.Server.Api.Rights
/// <summary>
/// User can change <see cref="Models.Internal.DreamDaemonLaunchParameters.HeartbeatSeconds"/>
/// </summary>
SetHeartbeatInterval = 1 << 12,
SetHealthCheckInterval = 1 << 12,
/// <summary>
/// User can create DreamDaemon process dumps or change <see cref="Models.Internal.DreamDaemonLaunchParameters.DumpOnHeartbeatRestart"/>.
@@ -46,7 +46,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
/// <summary>
/// Ping to ensure the server is running.
/// </summary>
Heartbeat,
HealthCheck,
/// <summary>
/// Notify the server of a reattach and potentially new version.
@@ -70,7 +70,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
or TopicCommandType.ChatChannelsUpdate
or TopicCommandType.ServerRestarted => true,
TopicCommandType.ChatCommand
or TopicCommandType.Heartbeat
or TopicCommandType.HealthCheck
or TopicCommandType.ReceiveChunk => false,
TopicCommandType.SendChunk => throw new InvalidOperationException("SendChunk topic priority should be based on the original TopicParameters!"),
_ => throw new InvalidOperationException($"Invalid value for {nameof(CommandType)}: {CommandType}"),
@@ -161,9 +161,9 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
/// <summary>
/// Initializes a new instance of the <see cref="TopicParameters"/> class.
/// </summary>
/// <remarks>Constructor for <see cref="TopicCommandType.Heartbeat"/>s.</remarks>
/// <remarks>Constructor for <see cref="TopicCommandType.HealthCheck"/>s.</remarks>
public TopicParameters()
: this(TopicCommandType.Heartbeat)
: this(TopicCommandType.HealthCheck)
{
}
@@ -170,7 +170,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
case MonitorActivationReason.ActiveServerPrimed:
await HandleEvent(EventType.WorldPrime, Enumerable.Empty<string>(), false, cancellationToken);
break;
case MonitorActivationReason.Heartbeat:
case MonitorActivationReason.HealthCheck:
default:
throw new InvalidOperationException($"Invalid activation reason: {reason}");
}
@@ -26,9 +26,9 @@
ActiveLaunchParametersUpdated,
/// <summary>
/// A heartbeat is required.
/// A health check is required.
/// </summary>
Heartbeat,
HealthCheck,
/// <summary>
/// Server primed.
@@ -1,6 +1,6 @@
# The Watchdog
The watchdog is what keeps DreamDaemon alive and well. It's responsible for starting, stopping, rebooting it, sending TGS events to the DMAPI, running heartbeats, and applying patches.
The watchdog is what keeps DreamDaemon alive and well. It's responsible for starting, stopping, rebooting it, sending TGS events to the DMAPI, running health checks, and applying patches.
There are three main implementations of the interface [IWatchdog](./IWatchdog.cs) that deal with how it handles the last of those jobs.
@@ -32,7 +32,7 @@ Watchdog monitoring takes place in the `MonitorLifetimes()` functiona and is ent
- When a server calls `/world/prod/Reboot()`.
- When a new .dmb is deployed.
- When DreamDaemon settings are changed via the API.
- When it is time to make a heartbeat.
- When it is time to make a health check.
Here's how this works.
@@ -155,7 +155,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <summary>
/// The number of hearbeats missed.
/// </summary>
int heartbeatsMissed;
int healthChecksMissed;
/// <summary>
/// If the servers should be released instead of shutdown.
@@ -517,7 +517,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
// since neither server is running, this is safe to do
LastLaunchParameters = ActiveLaunchParameters;
heartbeatsMissed = 0;
healthChecksMissed = 0;
try
{
@@ -653,7 +653,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <summary>
/// Handles the actions to take when the monitor has to "wake up".
/// </summary>
/// <param name="activationReason">The <see cref="MonitorActivationReason"/> that caused the invocation. Will never be <see cref="MonitorActivationReason.Heartbeat"/>.</param>
/// <param name="activationReason">The <see cref="MonitorActivationReason"/> that caused the invocation. Will never be <see cref="MonitorActivationReason.HealthCheck"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="MonitorAction"/> to take.</returns>
protected abstract Task<MonitorAction> HandleMonitorWakeup(
@@ -850,12 +850,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
UpdateMonitoredTasks();
var heartbeatSeconds = ActiveLaunchParameters.HeartbeatSeconds.Value;
var heartbeat = heartbeatSeconds == 0
var healthCheckSeconds = ActiveLaunchParameters.HeartbeatSeconds.Value;
var healthCheck = healthCheckSeconds == 0
|| !controller.DMApiAvailable
? Extensions.TaskExtensions.InfiniteTask()
: Task.Delay(
TimeSpan.FromSeconds(heartbeatSeconds),
TimeSpan.FromSeconds(healthCheckSeconds),
cancellationToken);
// cancel waiting if requested
@@ -863,7 +863,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
var toWaitOn = Task.WhenAny(
activeServerLifetime,
activeServerReboot,
heartbeat,
healthCheck,
newDmbAvailable,
cancelTcs.Task,
activeLaunchParametersChanged,
@@ -908,7 +908,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|| CheckActivationReason(ref activeServerReboot, MonitorActivationReason.ActiveServerRebooted)
|| CheckActivationReason(ref newDmbAvailable, MonitorActivationReason.NewDmbAvailable)
|| CheckActivationReason(ref activeLaunchParametersChanged, MonitorActivationReason.ActiveLaunchParametersUpdated)
|| CheckActivationReason(ref heartbeat, MonitorActivationReason.Heartbeat)
|| CheckActivationReason(ref healthCheck, MonitorActivationReason.HealthCheck)
|| CheckActivationReason(ref serverPrimed, MonitorActivationReason.ActiveServerPrimed);
UpdateMonitoredTasks();
@@ -918,8 +918,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
else
{
Logger.LogTrace("Reason: {activationReason}", activationReason);
if (activationReason == MonitorActivationReason.Heartbeat)
nextAction = await HandleHeartbeat(
if (activationReason == MonitorActivationReason.HealthCheck)
nextAction = await HandleHealthCheck(
cancellationToken);
else
nextAction = await HandleMonitorWakeup(
@@ -1029,26 +1029,26 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
/// <summary>
/// Handles a watchdog heartbeat.
/// Handles a watchdog health check.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the next <see cref="MonitorAction"/> to take.</returns>
async Task<MonitorAction> HandleHeartbeat(CancellationToken cancellationToken)
async Task<MonitorAction> HandleHealthCheck(CancellationToken cancellationToken)
{
Logger.LogTrace("Sending heartbeat to active server...");
Logger.LogTrace("Sending health check to active server...");
var activeServer = GetActiveController();
var response = await activeServer.SendCommand(new TopicParameters(), cancellationToken);
var shouldShutdown = activeServer.RebootState == Session.RebootState.Shutdown;
if (response == null)
{
switch (++heartbeatsMissed)
switch (++healthChecksMissed)
{
case 1:
Logger.LogDebug("DEFCON 4: DreamDaemon missed first heartbeat!");
Logger.LogDebug("DEFCON 4: DreamDaemon missed first health check!");
break;
case 2:
const string message2 = "DEFCON 3: DreamDaemon has missed 2 heartbeats!";
const string message2 = "DEFCON 3: DreamDaemon has missed 2 health checks!";
Logger.LogInformation(message2);
Chat.QueueWatchdogMessage(message2);
break;
@@ -1056,7 +1056,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
var actionToTake = shouldShutdown
? "shutdown"
: "be restarted";
const string logTemplate1 = "DEFCON 2: DreamDaemon has missed 3 heartbeats! If it does not respond to the next one, the watchdog will {actionToTake}!";
const string logTemplate1 = "DEFCON 2: DreamDaemon has missed 3 health checks! If it does not respond to the next one, the watchdog will {actionToTake}!";
Logger.LogWarning(logTemplate1, actionToTake);
Chat.QueueWatchdogMessage(
logTemplate1.Replace(
@@ -1068,7 +1068,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
var actionTaken = shouldShutdown
? "Shutting down due to graceful termination request"
: "Restarting";
const string logTemplate2 = "DEFCON 1: Four heartbeats have been missed! {actionTaken}...";
const string logTemplate2 = "DEFCON 1: Four health checks have been missed! {actionTaken}...";
Logger.LogWarning(logTemplate2, actionTaken);
Chat.QueueWatchdogMessage(
logTemplate2.Replace(
@@ -1087,12 +1087,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
await DisposeAndNullControllers(cancellationToken);
return shouldShutdown ? MonitorAction.Exit : MonitorAction.Restart;
default:
Logger.LogError("Invalid heartbeats missed count: {heartbeatsMissed}", heartbeatsMissed);
Logger.LogError("Invalid health checks missed count: {healthChecksMissed}", healthChecksMissed);
break;
}
}
else
heartbeatsMissed = 0;
healthChecksMissed = 0;
return MonitorAction.Continue;
}
@@ -146,7 +146,7 @@ namespace Tgstation.Server.Host.Controllers
| DreamDaemonRights.SoftShutdown
| DreamDaemonRights.Start
| DreamDaemonRights.SetStartupTimeout
| DreamDaemonRights.SetHeartbeatInterval
| DreamDaemonRights.SetHealthCheckInterval
| DreamDaemonRights.SetTopicTimeout
| DreamDaemonRights.SetAdditionalParameters
| DreamDaemonRights.SetVisibility
@@ -220,7 +220,7 @@ namespace Tgstation.Server.Host.Controllers
|| (model.SoftRestart.HasValue && !AuthenticationContext.InstancePermissionSet.DreamDaemonRights.Value.HasFlag(DreamDaemonRights.SoftRestart))
|| (model.SoftShutdown.HasValue && !AuthenticationContext.InstancePermissionSet.DreamDaemonRights.Value.HasFlag(DreamDaemonRights.SoftShutdown))
|| CheckModified(x => x.StartupTimeout, DreamDaemonRights.SetStartupTimeout)
|| CheckModified(x => x.HeartbeatSeconds, DreamDaemonRights.SetHeartbeatInterval)
|| CheckModified(x => x.HeartbeatSeconds, DreamDaemonRights.SetHealthCheckInterval)
|| CheckModified(x => x.DumpOnHeartbeatRestart, DreamDaemonRights.CreateDump)
|| CheckModified(x => x.TopicRequestTimeout, DreamDaemonRights.SetTopicTimeout)
|| CheckModified(x => x.AdditionalParameters, DreamDaemonRights.SetAdditionalParameters)
+12 -1
View File
@@ -128,6 +128,10 @@ var/run_bridge_test
var/list/channels = TgsChatChannelInfo()
return "[length(channels)]"
var/tactics8 = data["tgs_integration_test_tactics8"]
if(tactics8)
return received_health_check ? "received health check" : "did not receive health check"
TgsChatBroadcast(new /datum/tgs_message_content("Recieved non-tgs topic: `[T]`"))
return "feck"
@@ -151,10 +155,17 @@ var/run_bridge_test
TgsChatBroadcast("World Rebooting")
TgsReboot()
var/received_health_check = FALSE
/datum/tgs_event_handler/impl
receive_health_checks = TRUE
/datum/tgs_event_handler/impl/HandleEvent(event_code, ...)
set waitfor = FALSE
if(event_code == TGS_EVENT_WATCHDOG_DETACH)
if(event_code == TGS_EVENT_HEALTH_CHECK)
received_health_check = TRUE
else if(event_code == TGS_EVENT_WATCHDOG_DETACH)
// hack hack, calling world.TgsChatChannelInfo() will try to delay until the channels come back
var/datum/tgs_api/v5/api = TGS_READ_GLOBAL(tgs)
if(length(api.chat_channels))
@@ -90,8 +90,8 @@ namespace Tgstation.Server.Tests.Live.Instance
await RunLongRunningTestThenUpdateWithNewDme(cancellationToken);
await RunLongRunningTestThenUpdateWithByondVersionSwitch(cancellationToken);
await RunHeartbeatTest(true, cancellationToken);
await RunHeartbeatTest(false, cancellationToken);
await RunHealthCheckTest(true, cancellationToken);
await RunHealthCheckTest(false, cancellationToken);
await InteropTestsForLongRunningDme(cancellationToken);
@@ -349,10 +349,10 @@ namespace Tgstation.Server.Tests.Live.Instance
Assert.AreEqual(string.Empty, daemonStatus.AdditionalParameters);
}
async Task RunHeartbeatTest(bool checkDump, CancellationToken cancellationToken)
async Task RunHealthCheckTest(bool checkDump, CancellationToken cancellationToken)
{
System.Console.WriteLine("TEST: WATCHDOG HEARTBEAT TEST");
// enable heartbeats
System.Console.WriteLine("TEST: WATCHDOG HEALTH CHECK TEST");
// enable health checks
await instanceClient.DreamDaemon.Update(new DreamDaemonRequest
{
HeartbeatSeconds = 1,
@@ -365,7 +365,7 @@ namespace Tgstation.Server.Tests.Live.Instance
CheckDDPriority(cancellationToken);
// lock on to DD and pause it so it can't heartbeat
// lock on to DD and pause it so it can't health check
var ddProcs = System.Diagnostics.Process.GetProcessesByName("DreamDaemon").Where(x => !x.HasExited).ToList();
if (ddProcs.Count != 1)
Assert.Fail($"Incorrect number of DD processes: {ddProcs.Count}");
@@ -382,10 +382,22 @@ namespace Tgstation.Server.Tests.Live.Instance
await using var ourProcessHandler = executor
.GetProcess(ddProc.Id);
// Ensure it's responding to heartbeats
// Ensure it's responding to health checks
await Task.WhenAny(Task.Delay(20000, cancellationToken), ourProcessHandler.Lifetime);
Assert.IsFalse(ddProc.HasExited);
// check DD agrees
var topicRequestResult = await TopicClient.SendTopic(
IPAddress.Loopback,
$"tgs_integration_test_tactics8=1",
TestLiveServer.DDPort,
cancellationToken);
Assert.IsNotNull(topicRequestResult);
Assert.AreEqual(TopicResponseType.StringResponse, topicRequestResult.ResponseType);
Assert.IsNotNull(topicRequestResult.StringData);
Assert.AreEqual(topicRequestResult.StringData, "received health check");
await instanceClient.DreamDaemon.Update(new DreamDaemonRequest
{
SoftShutdown = true
@@ -412,7 +424,7 @@ namespace Tgstation.Server.Tests.Live.Instance
}
while (timeout > 0);
// disable heartbeats
// disable health checks
await instanceClient.DreamDaemon.Update(new DreamDaemonRequest
{
HeartbeatSeconds = 0,