Skip to content

Commit 034c313

Browse files
authored
optimization: async dedicated server autosaving (#1473)
1 parent c2ea1fa commit 034c313

4 files changed

Lines changed: 143 additions & 0 deletions

File tree

Minecraft.Server/Windows64/ServerMain.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "../../Minecraft.World/TilePos.h"
2929
#include "../../Minecraft.World/compression.h"
3030
#include "../../Minecraft.World/OldChunkStorage.h"
31+
#include "../../Minecraft.World/ConsoleSaveFileOriginal.h"
3132
#include "../../Minecraft.World/net.minecraft.world.level.tile.h"
3233
#include "../../Minecraft.World/Random.h"
3334

@@ -325,6 +326,7 @@ static void TickCoreSystems()
325326
g_NetworkManager.DoWork();
326327
ProfileManager.Tick();
327328
StorageManager.Tick();
329+
ConsoleSaveFileOriginal::flushPendingBackgroundSave();
328330
}
329331

330332
/**
@@ -701,9 +703,20 @@ int main(int argc, char **argv)
701703
{
702704
C4JThread waitThread(&WaitForServerStoppedThreadProc, NULL, "WaitServerStopped");
703705
waitThread.Run();
706+
while (waitThread.isRunning())
707+
{
708+
TickCoreSystems();
709+
Sleep(10);
710+
}
704711
waitThread.WaitForCompletion(INFINITE);
705712
}
706713

714+
while (ConsoleSaveFileOriginal::hasPendingBackgroundSave())
715+
{
716+
TickCoreSystems();
717+
Sleep(10);
718+
}
719+
707720
LogInfof("shutdown", "Cleaning up and exiting.");
708721
WinsockNetLayer::Shutdown();
709722
LogDebugf("shutdown", "Network layer shutdown complete.");

Minecraft.Server/cmake/sources/Common.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,8 @@ set(_MINECRAFT_SERVER_COMMON_ROOT
494494
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.Client/iob_shim.asm"
495495
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.Client/stdafx.cpp"
496496
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.Client/stubs.cpp"
497+
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/ConsoleSaveFileOriginal.cpp"
498+
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/ConsoleSaveFileOriginal.h"
497499
"${CMAKE_CURRENT_SOURCE_DIR}/../include/lce_filesystem/lce_filesystem.cpp"
498500
"${CMAKE_CURRENT_SOURCE_DIR}/Console/ServerCliInput.cpp"
499501
"${CMAKE_CURRENT_SOURCE_DIR}/Console/ServerCliInput.h"

Minecraft.World/ConsoleSaveFileOriginal.cpp

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,26 @@
1212
#include "..\Minecraft.Client\Common\GameRules\LevelGenerationOptions.h"
1313
#include "..\Minecraft.World\net.minecraft.world.level.chunk.storage.h"
1414

15+
#ifdef MINECRAFT_SERVER_BUILD
16+
#include <thread>
17+
#include <atomic>
18+
#include <mutex>
19+
20+
static std::atomic<bool> s_bgSaveActive{false};
21+
static std::mutex s_bgSaveMutex;
22+
23+
struct BackgroundSaveResult
24+
{
25+
ConsoleSaveFile *owner = nullptr;
26+
PBYTE thumbData = nullptr;
27+
DWORD thumbSize = 0;
28+
BYTE textMeta[88] = {};
29+
int textMetaBytes = 0;
30+
bool pending = false;
31+
};
32+
static BackgroundSaveResult s_bgResult;
33+
#endif
34+
1535

1636
#ifdef _XBOX
1737
#define RESERVE_ALLOCATION MEM_RESERVE | MEM_LARGE_PAGES
@@ -673,6 +693,83 @@ void ConsoleSaveFileOriginal::Flush(bool autosave, bool updateThumbnail )
673693

674694
unsigned int fileSize = header.GetFileSize();
675695

696+
#ifdef MINECRAFT_SERVER_BUILD
697+
// on the server we dont want to block the tick thread doing compression!!!
698+
// sna[pshot pvSaveMem while we still hold the lock then hand it off to a background thread
699+
byte *snap = new (std::nothrow) byte[fileSize];
700+
if (snap)
701+
{
702+
// copy the save buffer while we still own the lock so nothing can write to it mid-copy
703+
QueryPerformanceCounter(&qwTime);
704+
memcpy(snap, pvSaveMem, fileSize);
705+
QueryPerformanceCounter(&qwNewTime);
706+
app.DebugPrintf("snapshot %u bytes in %.3f sec\n", fileSize,
707+
(qwNewTime.QuadPart - qwTime.QuadPart) * fSecsPerTick);
708+
709+
PBYTE thumb = nullptr;
710+
DWORD thumbSz = 0;
711+
app.GetSaveThumbnail(&thumb, &thumbSz);
712+
713+
BYTE meta[88];
714+
ZeroMemory(meta, 88);
715+
int64_t seed = 0;
716+
bool hasSeed = false;
717+
if (MinecraftServer *sv = MinecraftServer::getInstance(); sv && sv->levels[0])
718+
{
719+
seed = sv->levels[0]->getLevelData()->getSeed();
720+
hasSeed = true;
721+
}
722+
int metaLen = app.CreateImageTextData(meta, seed, hasSeed,
723+
app.GetGameHostOption(eGameHostOption_All), Minecraft::GetInstance()->getCurrentTexturePackId());
724+
725+
// telemetry
726+
INT uid = 0;
727+
StorageManager.GetSaveUniqueNumber(&uid);
728+
TelemetryManager->RecordLevelSaveOrCheckpoint(ProfileManager.GetPrimaryPad(), uid, fileSize);
729+
730+
ReleaseSaveAccess();
731+
s_bgSaveActive.store(true, std::memory_order_release);
732+
733+
std::thread([snap, fileSize, thumb, thumbSz, meta, metaLen, this]() {
734+
unsigned int compLen = fileSize + 8;
735+
byte *buf = static_cast<byte *>(StorageManager.AllocateSaveData(compLen));
736+
if (!buf)
737+
{
738+
// FAIL!! attempt precalc
739+
compLen = 0;
740+
Compression::getCompression()->Compress(nullptr, &compLen, snap, fileSize);
741+
compLen += 8;
742+
buf = static_cast<byte *>(StorageManager.AllocateSaveData(compLen));
743+
}
744+
if (buf)
745+
{
746+
// COM,PRESS
747+
Compression::getCompression()->Compress(buf + 8, &compLen, snap, fileSize);
748+
ZeroMemory(buf, 8);
749+
memcpy(buf + 4, &fileSize, sizeof(fileSize));
750+
751+
// store the result so flushPendingBackgroundSave() can pick it up on the main thread next tick
752+
// StorageManager isnt thread safe so we cant call SetSaveImages or SaveSaveData from here. Bwoomp
753+
std::lock_guard<std::mutex> lk(s_bgSaveMutex);
754+
s_bgResult.owner = this;
755+
s_bgResult.thumbData = thumb;
756+
s_bgResult.thumbSize = thumbSz;
757+
memcpy(s_bgResult.textMeta, meta, sizeof(meta));
758+
s_bgResult.textMetaBytes = metaLen;
759+
s_bgResult.pending = true;
760+
}
761+
else
762+
{
763+
app.DebugPrintf("save buf alloc failed\n");
764+
s_bgSaveActive.store(false, std::memory_order_release);
765+
}
766+
delete[] snap;
767+
}).detach();
768+
return;
769+
}
770+
app.DebugPrintf("snapshot alloc failed (%u bytes)\n", fileSize);
771+
#endif
772+
676773
// Assume that the compression will make it smaller so initially attempt to allocate the current file size
677774
// We add 4 bytes to the start so that we can signal compressed data
678775
// And another 4 bytes to store the decompressed data size
@@ -838,6 +935,10 @@ int ConsoleSaveFileOriginal::SaveSaveDataCallback(LPVOID lpParam,bool bRes)
838935
{
839936
ConsoleSaveFile *pClass=static_cast<ConsoleSaveFile *>(lpParam);
840937

938+
#ifdef MINECRAFT_SERVER_BUILD
939+
s_bgSaveActive.store(false, std::memory_order_release);
940+
#endif
941+
841942
return 0;
842943
}
843944

@@ -1085,3 +1186,25 @@ void *ConsoleSaveFileOriginal::getWritePointer(FileEntry *file)
10851186
{
10861187
return static_cast<char *>(pvSaveMem) + file->currentFilePointer;;
10871188
}
1189+
1190+
#ifdef MINECRAFT_SERVER_BUILD
1191+
void ConsoleSaveFileOriginal::flushPendingBackgroundSave()
1192+
{
1193+
std::lock_guard<std::mutex> lk(s_bgSaveMutex);
1194+
if (!s_bgResult.pending)
1195+
return;
1196+
1197+
StorageManager.SetSaveImages(
1198+
s_bgResult.thumbData, s_bgResult.thumbSize,
1199+
nullptr, 0, s_bgResult.textMeta, s_bgResult.textMetaBytes);
1200+
StorageManager.SaveSaveData(&ConsoleSaveFileOriginal::SaveSaveDataCallback, s_bgResult.owner);
1201+
1202+
s_bgResult.pending = false;
1203+
// the actual write isnt done until SaveSaveDataCallback fires
1204+
}
1205+
1206+
bool ConsoleSaveFileOriginal::hasPendingBackgroundSave()
1207+
{
1208+
return s_bgSaveActive.load(std::memory_order_acquire);
1209+
}
1210+
#endif

Minecraft.World/ConsoleSaveFileOriginal.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ class ConsoleSaveFileOriginal : public ConsoleSaveFile
7777
virtual void LockSaveAccess();
7878
virtual void ReleaseSaveAccess();
7979

80+
#ifdef MINECRAFT_SERVER_BUILD
81+
static void flushPendingBackgroundSave();
82+
static bool hasPendingBackgroundSave();
83+
#endif
84+
8085
virtual ESavePlatform getSavePlatform();
8186
virtual bool isSaveEndianDifferent();
8287
virtual void setLocalPlatform();

0 commit comments

Comments
 (0)