2017-12-04 18:10:33 -06:00
using System ;
2019-12-05 02:46:04 -05:00
using System.Globalization ;
2017-12-12 07:08:32 -06:00
using System.IO ;
2020-02-29 16:58:29 -05:00
using System.Reflection ;
2024-02-03 20:47:14 +00:00
using System.Runtime ;
2018-09-15 15:26:10 -05:00
using System.Runtime.InteropServices ;
2019-12-05 02:46:04 -05:00
using System.Threading ;
2018-02-10 06:05:13 -06:00
using log4net ;
using log4net.Config ;
2017-05-05 10:26:35 -04:00
using ACE.Common ;
2024-09-18 00:30:35 -04:00
using ACE.Common.Extensions ;
2017-05-05 10:26:35 -04:00
using ACE.Database ;
using ACE.DatLoader ;
2018-02-08 17:05:02 -06:00
using ACE.Server.Command ;
using ACE.Server.Managers ;
2023-05-25 21:15:41 -05:00
using ACE.Server.Mods ;
2023-12-21 14:15:38 -05:00
using ACE.Server.Network.Managers ;
2020-02-29 16:58:29 -05:00
2018-02-08 17:05:02 -06:00
namespace ACE.Server
2017-05-05 10:26:35 -04:00
{
2020-03-02 04:24:47 -05:00
partial class Program
2017-05-05 10:26:35 -04:00
{
2018-09-15 15:26:10 -05:00
/// <summary>
/// The timeBeginPeriod function sets the minimum timer resolution for an application or device driver. Used to manipulate the timer frequency.
/// https://docs.microsoft.com/en-us/windows/desktop/api/timeapi/nf-timeapi-timebeginperiod
/// Important note: This function affects a global Windows setting. Windows uses the lowest value (that is, highest resolution) requested by any process.
/// </summary>
2024-08-20 16:26:08 -07:00
[DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")]
2018-09-15 15:26:10 -05:00
public static extern uint MM_BeginPeriod ( uint uMilliseconds ) ;
/// <summary>
/// The timeEndPeriod function clears a previously set minimum timer resolution
/// https://docs.microsoft.com/en-us/windows/desktop/api/timeapi/nf-timeapi-timeendperiod
/// </summary>
[DllImport("winmm.dll", EntryPoint = "timeEndPeriod")]
public static extern uint MM_EndPeriod ( uint uMilliseconds ) ;
2021-01-27 23:44:52 -05:00
private static readonly ILog log = LogManager . GetLogger ( MethodBase . GetCurrentMethod ( ) . DeclaringType ) ;
2017-05-05 10:26:35 -04:00
2020-03-02 04:24:47 -05:00
public static readonly bool IsRunningInContainer = Convert . ToBoolean ( Environment . GetEnvironmentVariable ( "DOTNET_RUNNING_IN_CONTAINER" ) ) ;
2017-05-05 10:26:35 -04:00
public static void Main ( string [ ] args )
{
2021-01-24 11:27:26 -05:00
var consoleTitle = $"ACEmulator - v{ServerBuildInfo.FullVersion}" ;
Console . Title = consoleTitle ;
2019-01-17 10:05:48 -06:00
AppDomain . CurrentDomain . UnhandledException + = CurrentDomain_UnhandledException ;
2017-05-05 10:26:35 -04:00
AppDomain . CurrentDomain . ProcessExit + = new EventHandler ( OnProcessExit ) ;
2020-01-03 20:05:51 -06:00
// Typically, you wouldn't force the current culture on an entire application unless you know sure your application is used in a specific region (which ACE is not)
// We do this because almost all of the client/user input/output code does not take culture into account, and assumes en-US formatting.
// Without this, many commands that require special characters like , and . will break
2019-12-05 02:46:04 -05:00
Thread . CurrentThread . CurrentCulture = new CultureInfo ( "en-US" ) ;
2020-01-03 20:05:51 -06:00
// Init our text encoding options. This will allow us to use more than standard ANSI text, which the client also supports.
2018-07-01 20:47:03 -07:00
System . Text . Encoding . RegisterProvider ( System . Text . CodePagesEncodingProvider . Instance ) ;
2020-01-03 20:05:51 -06:00
// Look for the log4net.config first in the current environment directory, then in the ExecutingAssembly location
2020-02-29 16:58:29 -05:00
var exeLocation = Path . GetDirectoryName ( Assembly . GetExecutingAssembly ( ) . Location ) ;
2020-03-02 04:24:47 -05:00
var containerConfigDirectory = "/ace/Config" ;
2020-02-29 16:58:29 -05:00
var log4netConfig = Path . Combine ( exeLocation , "log4net.config" ) ;
var log4netConfigExample = Path . Combine ( exeLocation , "log4net.config.example" ) ;
2020-03-02 04:24:47 -05:00
var log4netConfigContainer = Path . Combine ( containerConfigDirectory , "log4net.config" ) ;
if ( IsRunningInContainer & & File . Exists ( log4netConfigContainer ) )
File . Copy ( log4netConfigContainer , log4netConfig , true ) ;
2020-01-03 20:05:51 -06:00
var log4netFileInfo = new FileInfo ( "log4net.config" ) ;
if ( ! log4netFileInfo . Exists )
2020-02-29 16:58:29 -05:00
log4netFileInfo = new FileInfo ( log4netConfig ) ;
2020-02-23 17:58:20 -05:00
if ( ! log4netFileInfo . Exists )
{
2020-02-29 16:58:29 -05:00
var exampleFile = new FileInfo ( log4netConfigExample ) ;
2020-02-23 17:58:20 -05:00
if ( ! exampleFile . Exists )
{
Console . WriteLine ( "log4net Configuration file is missing. Please copy the file log4net.config.example to log4net.config and edit it to match your needs before running ACE." ) ;
throw new Exception ( "missing log4net configuration file" ) ;
}
else
{
2020-03-02 04:24:47 -05:00
if ( ! IsRunningInContainer )
{
Console . WriteLine ( "log4net Configuration file is missing, cloning from example file." ) ;
File . Copy ( log4netConfigExample , log4netConfig ) ;
}
else
2024-08-20 16:26:08 -07:00
{
2020-03-02 04:24:47 -05:00
if ( ! File . Exists ( log4netConfigContainer ) )
{
Console . WriteLine ( "log4net Configuration file is missing, ACEmulator is running in a container, cloning from docker file." ) ;
var log4netConfigDocker = Path . Combine ( exeLocation , "log4net.config.docker" ) ;
File . Copy ( log4netConfigDocker , log4netConfig ) ;
File . Copy ( log4netConfigDocker , log4netConfigContainer ) ;
}
else
{
File . Copy ( log4netConfigContainer , log4netConfig ) ;
}
}
2020-02-23 17:58:20 -05:00
}
}
2021-01-27 23:44:52 -05:00
var logRepository = LogManager . GetRepository ( Assembly . GetEntryAssembly ( ) ) ;
XmlConfigurator . ConfigureAndWatch ( logRepository , log4netFileInfo ) ;
2017-12-12 07:08:32 -06:00
Limit database from taking all the threads (#2261)
* Limit database from taking all the threads
This adds limits to the database thread consumption.
It also should allow the world to now consume threads easier.
The way it is done is as follows.
- We determine the number of available threads using Environment.ProcessCount
- We allocate (int)Math.Max(Environment.ProcessorCount * .34, 1) to the World, and the remainder tothe database.
It breaks down as follows
1 vCPU = 1 thread world, 1 thread database
2 vCPU = 1 thread world, 1 thread database
3 vCPU = 1 thread world, 2 thread database
4 vCPU = 1 thread world, 3 thread database
5 vCPU = 1 thread world, 4 thread database
6 vCPU = 2 thread world, 4 thread database
7 vCPU = 2 thread world, 5 thread database
8 vCPU = 2 thread world, 6 thread database
9 vCPU = 3 thread world, 6 thread database
10 vCPU = 3 thread world, 7 thread database
I'd like to get some feedback from this PR on various sized servers.
What you may notice is that loading a player may take slightly longer (very slightly).
What you will probably notice is no discenerable difference in-game.
What I want to make sure happens is that the world doesn't end up feeling more choppy due to the parallel processing of outbound network traffic. Hopefully the more fair thread distriubiton will help prevent thread starvation.
* quit if not enough vCPU
* Give World Manager thread AboveNormal priority.
* support 1 CPU
2019-09-08 16:27:01 -05:00
if ( Environment . ProcessorCount < 2 )
log . Warn ( "Only one vCPU was detected. ACE may run with limited performance. You should increase your vCPU count for anything more than a single player server." ) ;
2018-09-15 15:26:10 -05:00
// Do system specific initializations here
try
{
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
{
// On many windows systems, the default resolution for Thread.Sleep is 15.6ms. This allows us to command a tighter resolution
MM_BeginPeriod ( 1 ) ;
}
}
catch ( Exception ex )
{
log . Error ( ex . ToString ( ) ) ;
}
2017-05-05 10:26:35 -04:00
log . Info ( "Starting ACEmulator..." ) ;
2020-03-24 00:58:12 -04:00
if ( IsRunningInContainer )
log . Info ( "ACEmulator is running in a container..." ) ;
2024-08-20 16:26:08 -07:00
2020-03-02 04:24:47 -05:00
var configFile = Path . Combine ( exeLocation , "Config.js" ) ;
var configConfigContainer = Path . Combine ( containerConfigDirectory , "Config.js" ) ;
if ( IsRunningInContainer & & File . Exists ( configConfigContainer ) )
File . Copy ( configConfigContainer , configFile , true ) ;
2020-02-23 17:58:20 -05:00
if ( ! File . Exists ( configFile ) )
{
2020-03-02 04:24:47 -05:00
if ( ! IsRunningInContainer )
DoOutOfBoxSetup ( configFile ) ;
else
{
if ( ! File . Exists ( configConfigContainer ) )
{
DoOutOfBoxSetup ( configFile ) ;
File . Copy ( configFile , configConfigContainer ) ;
}
else
File . Copy ( configConfigContainer , configFile ) ;
}
2020-02-23 17:58:20 -05:00
}
2017-05-05 10:26:35 -04:00
2018-02-10 06:05:13 -06:00
log . Info ( "Initializing ConfigManager..." ) ;
2017-05-05 10:26:35 -04:00
ConfigManager . Initialize ( ) ;
2018-02-10 06:05:13 -06:00
2024-04-05 12:03:49 -05:00
log . Info ( "Initializing ModManager..." ) ;
ModManager . Initialize ( ) ;
2021-01-23 12:13:03 -07:00
if ( ConfigManager . Config . Server . WorldName ! = "ACEmulator" )
2021-01-24 11:27:26 -05:00
{
consoleTitle = $"{ConfigManager.Config.Server.WorldName} | {consoleTitle}" ;
Console . Title = consoleTitle ;
}
2021-01-23 12:13:03 -07:00
2019-09-27 23:01:25 -05:00
if ( ConfigManager . Config . Offline . PurgeDeletedCharacters )
{
2024-09-18 00:30:35 -04:00
log . Info ( $"Purging deleted characters, and their possessions, older than {ConfigManager.Config.Offline.PurgeDeletedCharactersDays} days ({DateTime.Now.AddDays(-ConfigManager.Config.Offline.PurgeDeletedCharactersDays).ToCommonString()})..." ) ;
2019-11-24 16:50:50 -06:00
ShardDatabaseOfflineTools . PurgeCharactersInParallel ( ConfigManager . Config . Offline . PurgeDeletedCharactersDays , out var charactersPurged , out var playerBiotasPurged , out var possessionsPurged ) ;
2019-09-27 23:01:25 -05:00
log . Info ( $"Purged {charactersPurged:N0} characters, {playerBiotasPurged:N0} player biotas and {possessionsPurged:N0} possessions." ) ;
}
2019-11-24 16:50:50 -06:00
if ( ConfigManager . Config . Offline . PurgeOrphanedBiotas )
{
log . Info ( $"Purging orphaned biotas..." ) ;
ShardDatabaseOfflineTools . PurgeOrphanedBiotasInParallel ( out var numberOfBiotasPurged ) ;
log . Info ( $"Purged {numberOfBiotasPurged:N0} biotas." ) ;
}
2021-08-15 18:41:28 -04:00
if ( ConfigManager . Config . Offline . PruneDeletedCharactersFromFriendLists )
{
log . Info ( $"Pruning invalid friends from all friend lists..." ) ;
ShardDatabaseOfflineTools . PruneDeletedCharactersFromFriendLists ( out var numberOfFriendsPruned ) ;
log . Info ( $"Pruned {numberOfFriendsPruned:N0} invalid friends found on friend lists." ) ;
}
if ( ConfigManager . Config . Offline . PruneDeletedObjectsFromShortcutBars )
{
log . Info ( $"Pruning invalid shortcuts from all shortcut bars..." ) ;
ShardDatabaseOfflineTools . PruneDeletedObjectsFromShortcutBars ( out var numberOfShortcutsPruned ) ;
log . Info ( $"Pruned {numberOfShortcutsPruned:N0} deleted objects found on shortcut bars." ) ;
}
if ( ConfigManager . Config . Offline . PruneDeletedCharactersFromSquelchLists )
{
log . Info ( $"Pruning invalid squelches from all squelch lists..." ) ;
ShardDatabaseOfflineTools . PruneDeletedCharactersFromSquelchLists ( out var numberOfSquelchesPruned ) ;
log . Info ( $"Pruned {numberOfSquelchesPruned:N0} invalid squelched characters found on squelch lists." ) ;
}
2022-05-07 11:30:42 -07:00
if ( ConfigManager . Config . Offline . AutoServerUpdateCheck )
CheckForServerUpdate ( ) ;
else
log . Info ( $"AutoServerVersionCheck is disabled..." ) ;
2020-05-08 01:44:21 -04:00
if ( ConfigManager . Config . Offline . AutoUpdateWorldDatabase )
{
CheckForWorldDatabaseUpdate ( ) ;
if ( ConfigManager . Config . Offline . AutoApplyWorldCustomizations )
AutoApplyWorldCustomizations ( ) ;
}
else
log . Info ( $"AutoUpdateWorldDatabase is disabled..." ) ;
if ( ConfigManager . Config . Offline . AutoApplyDatabaseUpdates )
AutoApplyDatabaseUpdates ( ) ;
else
log . Info ( $"AutoApplyDatabaseUpdates is disabled..." ) ;
2020-07-11 08:35:04 -05:00
// This should only be enabled manually. To enable it, simply uncomment this line
2021-12-24 15:31:26 +00:00
//ACE.Database.OfflineTools.Shard.BiotaGuidConsolidator.ConsolidateBiotaGuids(0xA0000000, true, false, out int numberOfBiotasConsolidated, out int numberOfBiotasSkipped, out int numberOfErrors);
//ACE.Database.OfflineTools.Shard.BiotaGuidConsolidator.ConsolidateBiotaGuids(0xD0000000, false, true, out int numberOfBiotasConsolidated2, out int numberOfBiotasSkipped2, out int numberOfErrors2);
2020-07-11 08:35:04 -05:00
2020-12-10 21:37:29 -05:00
ShardDatabaseOfflineTools . CheckForBiotaPropertiesPaletteOrderColumnInShard ( ) ;
2021-10-15 00:22:40 -04:00
// pre-load starterGear.json, abort startup if file is not found as it is required to create new characters.
if ( Factories . StarterGearFactory . GetStarterGearConfiguration ( ) = = null )
{
log . Fatal ( "Unable to load or parse starterGear.json. ACEmulator will now abort startup." ) ;
ServerManager . StartupAbort ( ) ;
Environment . Exit ( 0 ) ;
}
2018-02-10 06:05:13 -06:00
log . Info ( "Initializing ServerManager..." ) ;
2017-06-23 07:49:51 -05:00
ServerManager . Initialize ( ) ;
2018-02-10 06:05:13 -06:00
log . Info ( "Initializing DatManager..." ) ;
2018-11-20 07:45:09 -06:00
DatManager . Initialize ( ConfigManager . Config . Server . DatFilesDirectory , true ) ;
2018-02-10 06:05:13 -06:00
2023-06-09 13:15:10 -04:00
if ( ConfigManager . Config . DDD . EnableDATPatching )
{
log . Info ( "Initializing DDDManager..." ) ;
DDDManager . Initialize ( ) ;
}
else
log . Info ( "DAT Patching Disabled..." ) ;
2018-02-10 06:05:13 -06:00
log . Info ( "Initializing DatabaseManager..." ) ;
2017-05-05 10:26:35 -04:00
DatabaseManager . Initialize ( ) ;
2018-02-10 06:05:13 -06:00
2021-01-16 16:24:42 -05:00
if ( DatabaseManager . InitializationFailure )
{
log . Fatal ( "DatabaseManager initialization failed. ACEmulator will now abort startup." ) ;
ServerManager . StartupAbort ( ) ;
Environment . Exit ( 0 ) ;
}
2018-02-10 06:05:13 -06:00
log . Info ( "Starting DatabaseManager..." ) ;
2017-07-07 18:11:49 -05:00
DatabaseManager . Start ( ) ;
2018-02-10 06:05:13 -06:00
2018-08-29 15:36:49 -05:00
log . Info ( "Starting PropertyManager..." ) ;
PropertyManager . Initialize ( ) ;
2018-02-10 06:05:13 -06:00
log . Info ( "Initializing GuidManager..." ) ;
2017-07-07 18:11:49 -05:00
GuidManager . Initialize ( ) ;
2018-02-10 06:05:13 -06:00
2019-04-04 13:38:01 -05:00
if ( ConfigManager . Config . Server . ServerPerformanceMonitorAutoStart )
{
log . Info ( "Server Performance Monitor auto starting..." ) ;
ServerPerformanceMonitor . Start ( ) ;
}
2018-10-09 10:52:00 -05:00
if ( ConfigManager . Config . Server . WorldDatabasePrecaching )
{
log . Info ( "Precaching Weenies..." ) ;
2021-06-12 14:15:42 -05:00
DatabaseManager . World . CacheAllWeenies ( ) ;
2020-03-02 08:03:50 -06:00
log . Info ( "Precaching Cookbooks..." ) ;
2021-06-12 14:15:42 -05:00
DatabaseManager . World . CacheAllCookbooks ( ) ;
2020-03-02 08:03:50 -06:00
log . Info ( "Precaching Events..." ) ;
DatabaseManager . World . GetAllEvents ( ) ;
2019-02-11 13:35:21 -06:00
log . Info ( "Precaching House Portals..." ) ;
DatabaseManager . World . CacheAllHousePortals ( ) ;
2018-10-09 10:52:00 -05:00
log . Info ( "Precaching Points Of Interest..." ) ;
DatabaseManager . World . CacheAllPointsOfInterest ( ) ;
log . Info ( "Precaching Spells..." ) ;
DatabaseManager . World . CacheAllSpells ( ) ;
2020-03-02 08:03:50 -06:00
log . Info ( "Precaching Treasures - Death..." ) ;
DatabaseManager . World . CacheAllTreasuresDeath ( ) ;
log . Info ( "Precaching Treasures - Material Base..." ) ;
2020-10-20 12:07:18 -04:00
DatabaseManager . World . CacheAllTreasureMaterialBase ( ) ;
2020-03-02 08:03:50 -06:00
log . Info ( "Precaching Treasures - Material Groups..." ) ;
2020-10-20 12:07:18 -04:00
DatabaseManager . World . CacheAllTreasureMaterialGroups ( ) ;
2020-03-02 08:03:50 -06:00
log . Info ( "Precaching Treasures - Material Colors..." ) ;
2020-10-20 12:07:18 -04:00
DatabaseManager . World . CacheAllTreasureMaterialColor ( ) ;
2020-03-02 08:03:50 -06:00
log . Info ( "Precaching Treasures - Wielded..." ) ;
2020-10-20 12:07:18 -04:00
DatabaseManager . World . CacheAllTreasureWielded ( ) ;
2018-10-09 10:52:00 -05:00
}
else
Refactoring LandblockManager, improving VitalTick performance (#1079)
* Physics Cache cleanup (Mostly cosmetic)
* Vertex/Polygon/BSP CacneEnabled flags added, default to false
Testing results:
Debug mode, loading 31,329 landblocks (x = 0x20-0xD0, y = 0x20-0xD0)
With Vertex, Polygon & BSP Cache:
2018-10-25 10:05:12,121 DEBUG: Loaded Landblocks in 120.39 seconds
2018-10-25 10:05:12,218 DEBUG: 13759 MB used
2018-10-25 10:08:00,330 DEBUG: Loaded Landblocks in 101.11 seconds
2018-10-25 10:08:00,427 DEBUG: 13667 MB used
2018-10-25 10:13:08,448 DEBUG: Loaded Landblocks in 104.36 seconds
2018-10-25 10:13:08,507 DEBUG: 13666 MB used
With Vertex, Polygon & BSP Cache Disabled:
2018-10-25 10:17:55,339 DEBUG: Loaded Landblocks in 100.63 seconds
2018-10-25 10:17:55,404 DEBUG: 14175 MB used
2018-10-25 10:21:11,596 DEBUG: Loaded Landblocks in 89.23 seconds
2018-10-25 10:21:11,666 DEBUG: 14220 MB used
2018-10-25 10:27:22,145 DEBUG: Loaded Landblocks in 93.74 seconds
2018-10-25 10:27:22,200 DEBUG: 14206 MB used
* PhysicsCaches, remove redundant null check
* Improved GfxObjCache model
* DBObj Boxing/Unboxing improvements
* Add thread safety to GfxObjCache
* Landblock phsyics loading async
* Adding setup "caching"
* Redirecting properties to DatLoader
* Refactoring LandblockManager, resolving Physics Landblock loading bug
* Tabs vs. spaces
* Improving VitalTick performance
* Adding refactored AttributeFormula to CreatureSkill
* Disabling BSP cache
2018-10-28 09:56:48 -04:00
log . Info ( "Precaching World Database Disabled..." ) ;
2018-10-09 10:52:00 -05:00
2018-12-02 13:41:16 -06:00
log . Info ( "Initializing PlayerManager..." ) ;
PlayerManager . Initialize ( ) ;
2019-02-13 18:15:28 -05:00
log . Info ( "Initializing HouseManager..." ) ;
HouseManager . Initialize ( ) ;
2018-02-10 06:05:13 -06:00
log . Info ( "Initializing InboundMessageManager..." ) ;
InboundMessageManager . Initialize ( ) ;
log . Info ( "Initializing SocketManager..." ) ;
2017-05-05 10:26:35 -04:00
SocketManager . Initialize ( ) ;
2018-02-10 06:05:13 -06:00
log . Info ( "Initializing WorldManager..." ) ;
2017-05-05 10:26:35 -04:00
WorldManager . Initialize ( ) ;
2017-06-30 08:08:43 -04:00
2018-04-23 13:28:27 -04:00
log . Info ( "Initializing EventManager..." ) ;
EventManager . Initialize ( ) ;
2020-02-12 19:49:34 -06:00
// Free up memory before the server goes online. This can free up 6 GB+ on larger servers.
log . Info ( "Forcing .net garbage collection..." ) ;
2024-02-03 20:47:14 +00:00
for ( int i = 0 ; i < 10 ; i + + )
{
// https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals
// https://learn.microsoft.com/en-us/dotnet/api/system.runtime.gcsettings.largeobjectheapcompactionmode
GCSettings . LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode . CompactOnce ;
2020-02-12 19:49:34 -06:00
GC . Collect ( ) ;
2024-02-03 20:47:14 +00:00
}
2020-02-12 19:49:34 -06:00
2018-02-10 06:05:13 -06:00
// This should be last
log . Info ( "Initializing CommandManager..." ) ;
CommandManager . Initialize ( ) ;
2019-04-19 01:58:03 -05:00
2024-04-05 12:03:49 -05:00
//Register mod commands
log . Info ( "Registering ModManager commands..." ) ;
ModManager . RegisterCommands ( ) ;
ModManager . ListMods ( ) ;
2023-05-25 21:15:41 -05:00
2019-04-19 01:58:03 -05:00
if ( ! PropertyManager . GetBool ( "world_closed" , false ) . Item )
{
WorldManager . Open ( null ) ;
}
2017-05-05 10:26:35 -04:00
}
2019-01-17 10:05:48 -06:00
private static void CurrentDomain_UnhandledException ( object sender , UnhandledExceptionEventArgs e )
{
log . Error ( e . ExceptionObject ) ;
}
2017-05-05 10:26:35 -04:00
private static void OnProcessExit ( object sender , EventArgs e )
{
2020-03-02 04:24:47 -05:00
if ( ! IsRunningInContainer )
{
if ( ! ServerManager . ShutdownInitiated )
log . Warn ( "Unsafe server shutdown detected! Data loss is possible!" ) ;
2018-10-23 15:52:09 -05:00
2020-03-02 04:24:47 -05:00
PropertyManager . StopUpdating ( ) ;
DatabaseManager . Stop ( ) ;
2018-09-15 15:26:10 -05:00
2020-03-02 04:24:47 -05:00
// Do system specific cleanup here
try
{
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
{
MM_EndPeriod ( 1 ) ;
}
}
catch ( Exception ex )
2018-09-15 15:26:10 -05:00
{
2020-03-02 04:24:47 -05:00
log . Error ( ex . ToString ( ) ) ;
2018-09-15 15:26:10 -05:00
}
}
2020-03-02 04:24:47 -05:00
else
2018-09-15 15:26:10 -05:00
{
2020-03-02 04:24:47 -05:00
ServerManager . DoShutdownNow ( ) ;
DatabaseManager . Stop ( ) ;
2018-09-15 15:26:10 -05:00
}
2017-05-05 10:26:35 -04:00
}
}
}