Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for servers to periodically communicate with external services #3446

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Source/ACE.Common/GameConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,7 @@ public class GameConfiguration
public bool LandblockPreloading { get; set; }

public List<PreloadedLandblocks> PreloadedLandblocks { get; set; }

public HeartbeatDefaults Heartbeat { get; set; }
}
}
28 changes: 28 additions & 0 deletions Source/ACE.Common/HeartbeatDefaults.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Newtonsoft.Json;

namespace ACE.Common
{
public struct HeartbeatDefaults
{
/// <summary>
/// Whether hearbeats are enabled
/// </summary>
[System.ComponentModel.DefaultValue(true)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public bool Enabled { get; set; }

/// <summary>
/// The heartbeat endpoint
/// </summary>
[System.ComponentModel.DefaultValue("https://heartbeat.treestats.net")]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public string Endpoint { get; set; }

/// <summary>
/// The heartbeat interval, in seconds
/// </summary>
[System.ComponentModel.DefaultValue(500)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public int Interval { get; set; }
}
}
96 changes: 96 additions & 0 deletions Source/ACE.Server/Managers/HeartbeatManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System;

using log4net;

using ACE.Common.Performance;
using System.Net.Http;
using System.Text;
using ACE.Common;
using Newtonsoft.Json;

namespace ACE.Server.Managers
{
public static class HeartbeatManager
{
private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

/// <summary>
/// The rate at which HeartbeatManager.Tick() executes
/// </summary>
private static RateLimiter updateHeartbeatManagerRateLimiter;

/// <summary>
/// Endpoint to send heartbeats to
/// </summary>
private static Uri endpoint;

public static void Initialize()
{
//if (!ConfigManager.Config.Server.Heartbeat.Enabled)
//{
// log.Debug("Not starting HeartbeatManager because it's disabled in config");

// return;
//}

// endpoint = new Uri(ConfigManager.Config.Server.Heartbeat.Endpoint);
endpoint = new Uri("https://treestats-servers.herokuapp.com/");

// updateHeartbeatManagerRateLimiter = new RateLimiter(1, TimeSpan.FromSeconds(ConfigManager.Config.Server.Heartbeat.Interval));
updateHeartbeatManagerRateLimiter = new RateLimiter(1, TimeSpan.FromSeconds(10));
}

/// <summary>
/// One-off class to help serialize the heartbeat's JSON payload
/// </summary>
private class HeartbeatPayload
{
public string WorldName;
public int OnlineCount;
}

/// <summary>
/// Runs at intervals according to config
/// </summary>
public static void Tick()
{

if (updateHeartbeatManagerRateLimiter.GetSecondsToWaitBeforeNextEvent() > 0)
return;

log.Debug($"HeartbeatManager.Tick()");

updateHeartbeatManagerRateLimiter.RegisterEvent();
DoHeartbeat();
}

public static void DoHeartbeat()
{
using (var client = new HttpClient())
{
HttpResponseMessage response;

try
{
HeartbeatPayload body = new HeartbeatPayload
{
OnlineCount = PlayerManager.GetOnlineCount(),
WorldName = ConfigManager.Config.Server.WorldName
};

HttpContent content = new StringContent(JsonConvert.SerializeObject(body));
response = client.PostAsync(endpoint, content).Result;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessing Result will block. Is it safe to do this on this thread?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I thought this was fine but can change if this is problematic. Thanks for catching this @enknamel


if (!response.IsSuccessStatusCode)
{
log.Debug("Heartbeat Failed: " + response.Content);
}
}
catch (Exception e)
{
log.Debug("Exception while sending Heartbeat: " + e.Message);
}
}
}
}
}
2 changes: 2 additions & 0 deletions Source/ACE.Server/Managers/WorldManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,8 @@ private static void UpdateWorld()
Thread.Sleep(sessionCount == 0 ? 10 : 1); // Relax the CPU more if no sessions are connected

Timers.PortalYearTicks += worldTickTimer.Elapsed.TotalSeconds;

HeartbeatManager.Tick();
}

// World has finished operations and concedes the thread to garbage collection
Expand Down
3 changes: 3 additions & 0 deletions Source/ACE.Server/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ public static void Main(string[] args)
log.Info("Starting PropertyManager...");
PropertyManager.Initialize();

log.Info("Starting HeartbeatManager...");
HeartbeatManager.Initialize();

log.Info("Initializing GuidManager...");
GuidManager.Initialize();

Expand Down