Refactored server stop and world save operations fixing race conditions so as to ensure operations always happen in a predicable order. This fixes output not appearing in the console / log for example. This adds TShock.Utils.StopServer method used by IGA, rcon and the RestAPI.
Fixed console title set not working Optimised command line parsing Made Utils a singleton to enforce the fact that only one copy should ever exist Added name to /v2/user/read output as users can be found by id
This commit is contained in:
parent
84789ff4d5
commit
d34199b17d
7 changed files with 228 additions and 121 deletions
|
|
@ -63,11 +63,7 @@ namespace TShockAPI
|
|||
TShock.Utils.Broadcast("Server map saving, potential lag spike");
|
||||
Console.WriteLine("Backing up world...");
|
||||
|
||||
Thread SaveWorld = new Thread(TShock.Utils.SaveWorld);
|
||||
SaveWorld.Start();
|
||||
|
||||
while (SaveWorld.ThreadState == ThreadState.Running)
|
||||
Thread.Sleep(50);
|
||||
SaveManager.Instance.SaveWorld();
|
||||
Console.WriteLine("World backed up");
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
Log.Info(string.Format("World backed up ({0})", Main.worldPathName));
|
||||
|
|
|
|||
|
|
@ -1007,38 +1007,37 @@ namespace TShockAPI
|
|||
}
|
||||
}
|
||||
|
||||
TShock.Utils.ForceKickAll("Server shutting down!");
|
||||
WorldGen.saveWorld();
|
||||
Netplay.disconnect = true;
|
||||
TShock.Utils.StopServer();
|
||||
}
|
||||
//Added restart command
|
||||
private static void Restart(CommandArgs args)
|
||||
{
|
||||
if (Main.runningMono){
|
||||
Log.ConsoleInfo("Sorry, this command has not yet been implemented in Mono");
|
||||
}else{
|
||||
if (TShock.Config.ServerSideInventory)
|
||||
{
|
||||
foreach (TSPlayer player in TShock.Players)
|
||||
{
|
||||
if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan)
|
||||
{
|
||||
TShock.InventoryDB.InsertPlayerData(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
//Added restart command
|
||||
private static void Restart(CommandArgs args)
|
||||
{
|
||||
if (Main.runningMono)
|
||||
{
|
||||
Log.ConsoleInfo("Sorry, this command has not yet been implemented in Mono");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TShock.Config.ServerSideInventory)
|
||||
{
|
||||
foreach (TSPlayer player in TShock.Players)
|
||||
{
|
||||
if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan)
|
||||
{
|
||||
TShock.InventoryDB.InsertPlayerData(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TShock.Utils.ForceKickAll("Server restarting!");
|
||||
WorldGen.saveWorld();
|
||||
Netplay.disconnect = true;
|
||||
System.Diagnostics.Process.Start(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
|
||||
Environment.Exit(0);
|
||||
}}
|
||||
TShock.Utils.StopServer();
|
||||
System.Diagnostics.Process.Start(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OffNoSave(CommandArgs args)
|
||||
{
|
||||
TShock.Utils.ForceKickAll("Server shutting down!");
|
||||
Netplay.disconnect = true;
|
||||
TShock.Utils.StopServer(false);
|
||||
}
|
||||
|
||||
private static void CheckUpdates(CommandArgs args)
|
||||
|
|
@ -2258,10 +2257,7 @@ namespace TShockAPI
|
|||
{
|
||||
Main.spawnTileX = args.Player.TileX + 1;
|
||||
Main.spawnTileY = args.Player.TileY + 3;
|
||||
|
||||
TShock.Utils.Broadcast("Server map saving, potential lag spike");
|
||||
Thread SaveWorld = new Thread(TShock.Utils.SaveWorld);
|
||||
SaveWorld.Start();
|
||||
SaveManager.Instance.SaveWorld(false);
|
||||
}
|
||||
|
||||
private static void Reload(CommandArgs args)
|
||||
|
|
@ -2288,9 +2284,7 @@ namespace TShockAPI
|
|||
|
||||
private static void Save(CommandArgs args)
|
||||
{
|
||||
TShock.Utils.Broadcast("Server map saving, potential lag spike");
|
||||
Thread SaveWorld = new Thread(TShock.Utils.SaveWorld);
|
||||
SaveWorld.Start();
|
||||
SaveManager.Instance.SaveWorld(false);
|
||||
}
|
||||
|
||||
private static void Settle(CommandArgs args)
|
||||
|
|
|
|||
|
|
@ -253,9 +253,7 @@ namespace TShockAPI
|
|||
WorldGen.genRand = new Random();
|
||||
if (text.StartsWith("exit"))
|
||||
{
|
||||
TShock.Utils.ForceKickAll("Server shutting down!");
|
||||
WorldGen.saveWorld(false);
|
||||
Netplay.disconnect = true;
|
||||
TShock.Utils.StopServer();
|
||||
return "Server shutting down.";
|
||||
}
|
||||
else if (text.StartsWith("playing") || text.StartsWith("/playing"))
|
||||
|
|
|
|||
|
|
@ -102,16 +102,10 @@ namespace TShockAPI
|
|||
if (!GetBool(parameters["confirm"], false))
|
||||
return RestInvalidParam("confirm");
|
||||
|
||||
if (!GetBool(parameters["nosave"], false))
|
||||
WorldGen.saveWorld();
|
||||
Netplay.disconnect = true;
|
||||
|
||||
// Inform players the server is shutting down
|
||||
var msg = string.IsNullOrWhiteSpace(parameters["message"]) ? "Server is shutting down" : parameters["message"];
|
||||
foreach (TSPlayer player in TShock.Players.Where(p => null != p))
|
||||
{
|
||||
TShock.Utils.ForceKick(player, msg);
|
||||
}
|
||||
TShock.Utils.StopServer(!GetBool(parameters["nosave"], false), msg);
|
||||
|
||||
return RestResponse("The server is shutting down");
|
||||
}
|
||||
|
||||
|
|
@ -304,8 +298,8 @@ namespace TShockAPI
|
|||
if (ret is RestObject)
|
||||
return ret;
|
||||
|
||||
User user = (User)ret;
|
||||
return new RestObject() { { "group", user.Group }, { "id", user.ID.ToString() } };
|
||||
User user = (User)ret;
|
||||
return new RestObject() { { "group", user.Group }, { "id", user.ID.ToString() }, { "name", user.Name } };
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
@ -411,7 +405,7 @@ namespace TShockAPI
|
|||
|
||||
private object WorldSave(RestVerbs verbs, IParameterCollection parameters)
|
||||
{
|
||||
TShock.Utils.SaveWorld();
|
||||
SaveManager.Instance.SaveWorld();
|
||||
|
||||
return RestResponse("World saved");
|
||||
}
|
||||
|
|
|
|||
123
TShockAPI/SaveManager.cs
Normal file
123
TShockAPI/SaveManager.cs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
using Terraria;
|
||||
|
||||
namespace TShockAPI
|
||||
{
|
||||
class SaveManager : IDisposable
|
||||
{
|
||||
// Singleton
|
||||
private static readonly SaveManager instance = new SaveManager();
|
||||
private SaveManager()
|
||||
{
|
||||
_saveThread = new Thread(SaveWorker);
|
||||
_saveThread.Name = "TShock SaveManager Worker";
|
||||
_saveThread.Start();
|
||||
}
|
||||
public static SaveManager Instance { get { return instance; } }
|
||||
|
||||
// Producer Consumer
|
||||
private EventWaitHandle _wh = new AutoResetEvent(false);
|
||||
private Object _saveLock = new Object();
|
||||
private Queue<SaveTask> _saveQueue = new Queue<SaveTask>();
|
||||
private Thread _saveThread;
|
||||
private int saveQueueCount { get { lock (_saveLock) return _saveQueue.Count; } }
|
||||
|
||||
/// <summary>
|
||||
/// SaveWorld event handler which notifies users that the server may lag
|
||||
/// </summary>
|
||||
public void OnSaveWorld(bool resettime = false, HandledEventArgs e = null)
|
||||
{
|
||||
TShock.Utils.Broadcast("Saving world. Momentary lag might result from this.", Color.Red);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the map data
|
||||
/// </summary>
|
||||
/// <param name="wait">wait for all pending saves to finish (default: true)</param>
|
||||
/// <param name="resetTime">reset the last save time counter (default: false)</param>
|
||||
/// <param name="direct">use the realsaveWorld method instead of saveWorld event (default: false)</param>
|
||||
public void SaveWorld(bool wait = true, bool resetTime = false, bool direct = false)
|
||||
{
|
||||
EnqueueTask(new SaveTask(resetTime, direct));
|
||||
if (!wait)
|
||||
return;
|
||||
|
||||
// Wait for all outstanding saves to complete
|
||||
int count = saveQueueCount;
|
||||
while (0 != count)
|
||||
{
|
||||
Thread.Sleep(50);
|
||||
count = saveQueueCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes any outstanding saves, shutsdown the save thread and returns
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
EnqueueTask(null);
|
||||
_saveThread.Join();
|
||||
_wh.Close();
|
||||
}
|
||||
|
||||
private void EnqueueTask(SaveTask task)
|
||||
{
|
||||
lock (_saveLock)
|
||||
{
|
||||
_saveQueue.Enqueue(task);
|
||||
}
|
||||
_wh.Set();
|
||||
}
|
||||
|
||||
private void SaveWorker()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
lock (_saveLock)
|
||||
{
|
||||
// NOTE: lock for the entire process so wait works in SaveWorld
|
||||
if (_saveQueue.Count > 0)
|
||||
{
|
||||
SaveTask task = _saveQueue.Dequeue();
|
||||
if (null == task)
|
||||
return;
|
||||
else
|
||||
{
|
||||
if (task.direct)
|
||||
{
|
||||
OnSaveWorld();
|
||||
WorldGen.realsaveWorld(task.resetTime);
|
||||
}
|
||||
else
|
||||
WorldGen.saveWorld(task.resetTime);
|
||||
TShock.Utils.Broadcast("World saved.", Color.Yellow);
|
||||
Log.Info(string.Format("World saved at ({0})", Main.worldPathName));
|
||||
}
|
||||
}
|
||||
}
|
||||
_wh.WaitOne();
|
||||
}
|
||||
}
|
||||
|
||||
class SaveTask
|
||||
{
|
||||
public bool resetTime { get; set; }
|
||||
public bool direct { get; set; }
|
||||
public SaveTask(bool resetTime, bool direct)
|
||||
{
|
||||
this.resetTime = resetTime;
|
||||
this.direct = direct;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("resetTime {0}, direct {1}", resetTime, direct);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ namespace TShockAPI
|
|||
public static GeoIPCountry Geo;
|
||||
public static SecureRest RestApi;
|
||||
public static RestManager RestManager;
|
||||
public static Utils Utils = new Utils();
|
||||
public static Utils Utils = Utils.Instance;
|
||||
public static StatTracker StatTracker = new StatTracker();
|
||||
/// <summary>
|
||||
/// Used for implementing REST Tokens prior to the REST system starting up.
|
||||
|
|
@ -186,7 +186,6 @@ namespace TShockAPI
|
|||
if (Config.EnableGeoIP && File.Exists(geoippath))
|
||||
Geo = new GeoIPCountry(geoippath);
|
||||
|
||||
Console.Title = string.Format("TerrariaShock Version {0} ({1})", Version, VersionCodename);
|
||||
Log.ConsoleInfo(string.Format("TerrariaShock Version {0} ({1}) now running.", Version, VersionCodename));
|
||||
|
||||
GameHooks.PostInitialize += OnPostInit;
|
||||
|
|
@ -203,7 +202,7 @@ namespace TShockAPI
|
|||
NpcHooks.SetDefaultsInt += OnNpcSetDefaults;
|
||||
ProjectileHooks.SetDefaults += OnProjectileSetDefaults;
|
||||
WorldHooks.StartHardMode += OnStartHardMode;
|
||||
WorldHooks.SaveWorld += OnSaveWorld;
|
||||
WorldHooks.SaveWorld += SaveManager.Instance.OnSaveWorld;
|
||||
|
||||
GetDataHandlers.InitGetDataHandler();
|
||||
Commands.InitCommands();
|
||||
|
|
@ -260,10 +259,13 @@ namespace TShockAPI
|
|||
{
|
||||
if (disposing)
|
||||
{
|
||||
// NOTE: order is important here
|
||||
if (Geo != null)
|
||||
{
|
||||
Geo.Dispose();
|
||||
}
|
||||
SaveManager.Instance.Dispose();
|
||||
|
||||
GameHooks.PostInitialize -= OnPostInit;
|
||||
GameHooks.Update -= OnUpdate;
|
||||
ServerHooks.Connect -= OnConnect;
|
||||
|
|
@ -278,15 +280,16 @@ namespace TShockAPI
|
|||
NpcHooks.SetDefaultsInt -= OnNpcSetDefaults;
|
||||
ProjectileHooks.SetDefaults -= OnProjectileSetDefaults;
|
||||
WorldHooks.StartHardMode -= OnStartHardMode;
|
||||
WorldHooks.SaveWorld -= OnSaveWorld;
|
||||
WorldHooks.SaveWorld -= SaveManager.Instance.OnSaveWorld;
|
||||
|
||||
if (File.Exists(Path.Combine(SavePath, "tshock.pid")))
|
||||
{
|
||||
File.Delete(Path.Combine(SavePath, "tshock.pid"));
|
||||
}
|
||||
|
||||
RestApi.Dispose();
|
||||
Log.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
|
|
@ -322,7 +325,7 @@ namespace TShockAPI
|
|||
if (Main.worldPathName != null && Config.SaveWorldOnCrash)
|
||||
{
|
||||
Main.worldPathName += ".crash";
|
||||
WorldGen.saveWorld();
|
||||
SaveManager.Instance.SaveWorld();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -361,36 +364,33 @@ namespace TShockAPI
|
|||
{
|
||||
for (int i = 0; i < parms.Length; i++)
|
||||
{
|
||||
if (parms[i].ToLower() == "-port")
|
||||
switch(parms[i].ToLower())
|
||||
{
|
||||
int port = Convert.ToInt32(parms[++i]);
|
||||
Netplay.serverPort = port;
|
||||
Config.ServerPort = port;
|
||||
OverridePort = true;
|
||||
Log.ConsoleInfo("Port overridden by startup argument. Set to " + port);
|
||||
}
|
||||
if (parms[i].ToLower() == "-rest-token")
|
||||
{
|
||||
string token = Convert.ToString(parms[++i]);
|
||||
RESTStartupTokens.Add(token, "null");
|
||||
Console.WriteLine("Startup parameter overrode REST token.");
|
||||
}
|
||||
if (parms[i].ToLower() == "-rest-enabled")
|
||||
{
|
||||
Config.RestApiEnabled = Convert.ToBoolean(parms[++i]);
|
||||
Console.WriteLine("Startup parameter overrode REST enable.");
|
||||
|
||||
}
|
||||
if (parms[i].ToLower() == "-rest-port")
|
||||
{
|
||||
Config.RestApiPort = Convert.ToInt32(parms[++i]);
|
||||
Console.WriteLine("Startup parameter overrode REST port.");
|
||||
|
||||
}
|
||||
if ((parms[i].ToLower() == "-maxplayers")||(parms[i].ToLower() == "-players"))
|
||||
{
|
||||
Config.MaxSlots = Convert.ToInt32(parms[++i]);
|
||||
Console.WriteLine("Startup parameter overrode maximum player slot configuration value.");
|
||||
case "-port":
|
||||
int port = Convert.ToInt32(parms[++i]);
|
||||
Netplay.serverPort = port;
|
||||
Config.ServerPort = port;
|
||||
OverridePort = true;
|
||||
Log.ConsoleInfo("Port overridden by startup argument. Set to " + port);
|
||||
break;
|
||||
case "-rest-token":
|
||||
string token = Convert.ToString(parms[++i]);
|
||||
RESTStartupTokens.Add(token, "null");
|
||||
Console.WriteLine("Startup parameter overrode REST token.");
|
||||
break;
|
||||
case "-rest-enabled":
|
||||
Config.RestApiEnabled = Convert.ToBoolean(parms[++i]);
|
||||
Console.WriteLine("Startup parameter overrode REST enable.");
|
||||
break;
|
||||
case "-rest-port":
|
||||
Config.RestApiPort = Convert.ToInt32(parms[++i]);
|
||||
Console.WriteLine("Startup parameter overrode REST port.");
|
||||
break;
|
||||
case "-maxplayers":
|
||||
case "-players":
|
||||
Config.MaxSlots = Convert.ToInt32(parms[++i]);
|
||||
Console.WriteLine("Startup parameter overrode maximum player slot configuration value.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -404,6 +404,7 @@ namespace TShockAPI
|
|||
|
||||
private void OnPostInit()
|
||||
{
|
||||
SetConsoleTitle();
|
||||
if (!File.Exists(Path.Combine(SavePath, "auth.lck")) && !File.Exists(Path.Combine(SavePath, "authcode.txt")))
|
||||
{
|
||||
var r = new Random((int) DateTime.Now.ToBinary());
|
||||
|
|
@ -465,7 +466,6 @@ namespace TShockAPI
|
|||
StatTracker.CheckIn();
|
||||
if (Backups.IsBackupTime)
|
||||
Backups.Backup();
|
||||
|
||||
//call these every second, not every update
|
||||
if ((DateTime.UtcNow - LastCheck).TotalSeconds >= 1)
|
||||
{
|
||||
|
|
@ -590,8 +590,13 @@ namespace TShockAPI
|
|||
}
|
||||
}
|
||||
}
|
||||
Console.Title = string.Format("TerrariaShock Version {0} ({1}) ({2}/{3})", Version, VersionCodename, count,
|
||||
Config.MaxSlots);
|
||||
SetConsoleTitle();
|
||||
}
|
||||
|
||||
private void SetConsoleTitle()
|
||||
{
|
||||
Console.Title = string.Format("{0} - {1}/{2} @ {3}:{4} (TerrariaShock v{5})", Config.ServerName, Utils.ActivePlayers(),
|
||||
Config.MaxSlots, Netplay.serverListenIP, Config.ServerPort, Version);
|
||||
}
|
||||
|
||||
private void OnConnect(int ply, HandledEventArgs handler)
|
||||
|
|
@ -1012,17 +1017,6 @@ namespace TShockAPI
|
|||
e.Handled = true;
|
||||
}
|
||||
|
||||
void OnSaveWorld(bool resettime, HandledEventArgs e)
|
||||
{
|
||||
if (!Utils.saving)
|
||||
{
|
||||
Utils.Broadcast("Saving world. Momentary lag might result from this.", Color.Red);
|
||||
var SaveWorld = new Thread(Utils.SaveWorld);
|
||||
SaveWorld.Start();
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Useful stuff:
|
||||
* */
|
||||
|
|
|
|||
|
|
@ -29,11 +29,10 @@ namespace TShockAPI
|
|||
{
|
||||
public class Utils
|
||||
{
|
||||
public static bool saving = false;
|
||||
|
||||
public Utils()
|
||||
{
|
||||
}
|
||||
// Utils is a Singleton
|
||||
private static readonly Utils instance = new Utils();
|
||||
private Utils() {}
|
||||
public static Utils Instance { get { return instance; } }
|
||||
|
||||
public Random Random = new Random();
|
||||
//private static List<Group> groups = new List<Group>();
|
||||
|
|
@ -135,11 +134,7 @@ namespace TShockAPI
|
|||
/// </summary>
|
||||
public void SaveWorld()
|
||||
{
|
||||
saving = true;
|
||||
WorldGen.realsaveWorld();
|
||||
Broadcast("World saved.", Color.Yellow);
|
||||
Log.Info(string.Format("World saved at ({0})", Main.worldPathName));
|
||||
saving = false;
|
||||
SaveManager.Instance.SaveWorld();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -186,15 +181,7 @@ namespace TShockAPI
|
|||
/// <returns>int playerCount</returns>
|
||||
public int ActivePlayers()
|
||||
{
|
||||
int num = 0;
|
||||
foreach (TSPlayer player in TShock.Players)
|
||||
{
|
||||
if (player != null && player.Active)
|
||||
{
|
||||
num++;
|
||||
}
|
||||
}
|
||||
return num;
|
||||
return Main.player.Where(p => null != p && p.active).Count();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -510,6 +497,27 @@ namespace TShockAPI
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the server after kicking all players with a reason message, and optionally saving the world
|
||||
/// </summary>
|
||||
/// <param name="save">bool perform a world save before stop (default: true)</param>
|
||||
/// <param name="reason">string reason (default: "Server shutting down!")</param>
|
||||
public void StopServer(bool save = true, string reason = "Server shutting down!")
|
||||
{
|
||||
ForceKickAll(reason);
|
||||
if (save)
|
||||
SaveManager.Instance.SaveWorld();
|
||||
|
||||
// Save takes a while so kick again
|
||||
ForceKickAll(reason);
|
||||
|
||||
// Broadcast so console can see we are shutting down as well
|
||||
TShock.Utils.Broadcast(reason, Color.Red);
|
||||
|
||||
// Disconnect after kick as that signifies server is exiting and could cause a race
|
||||
Netplay.disconnect = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kicks a player from the server without checking for immunetokick permission.
|
||||
/// </summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue