Shutdown middle of Saving

What happens if you SetAsync to a key and the game shuts down in the middle of it?

It will either save or not save, there’s no “in between” states if that’s what you’re wondering. The game server doesn’t do the writing to the database, that’s the job of a tertiary service that you have no control over. If the server crashes/shutting down in the same frame that you call SetAsync, the SetAsync call is probably not submitted to the tertiary service. If it crashes a frame or two later, the call would probably go through even though your server is dead/shut down.

So people’s data can potentially be lost and its inevitable

You should do regular autosaves for each player (i.e. once every 1-3 minutes) to reduce the chances of losing data on server crashes, and also save data on game::BindToClose to prevent loss of data due to server shutdown.

6 Likes

Yeah that was the issue that I was having

I was wondering what happens if a player is in an autosave cycle and the player leaves or the game shutsdown and it calls the saving function again? (While still saving) Should i have a check for this?

You could make it so that it doesn’t save data if you haven’t changed it. You’d have to write some code for keeping track of that yourself, i.e. keep a “changed” variable for each player and set that to true whenever you adjust the data. That way, you wouldn’t double-save in those situations and you’d avoid the “must be 6 seconds between writes on the same key” rule.

1 Like

And i was wondering what happens if its taking a while to save (i have a retry function with pcall incase of failures) and the player rejoins and tries loading in their data?

You could check, before every try, if the Player object is still parented to something (if player.Parent then), and if not, break out of the retry loop and don’t attempt auto-saving.

I don’t think it will matter much for the “save on leave” one. It’s not likely that a player will rejoin the same server within such a retry interval and if so, you could jump out of that retry mechanism once you noticed that the player has rejoined with a different Player object or so.

1 Like

I asked a few posts up

I was wondering what happens if a player is in an autosave cycle and the player leaves or the game shutsdown and it calls the saving function again? (While still saving) Should i have a check for this?

Can you please reclarify what to do in this case?

autosaving should run every few minutes to prevent major data losses (i.e. if datastores crash right when you leave). you should also have saving on leave in case they are working to store the most up-to-date data. you need to also add a bind with BindToClose to make sure it tries a few times to save the last people in the servers data before the server shuts down bc otherwise it might shut down before their data has been saved.

I’m still wondering what what happens if it’s currently saving from the auto save cycle and you leave and it kicks in another save. They both save one after the other?

Depends on if your autosave code ever yields.

If your autosave code quickly and asynchronously sends off data to save, then you wont run into any problems because in Roblox Lua isn’t multithreaded (if you are running, then you are the only Lua code running).

If your autosave code yields, then the game may close while your code is yielding. To prevent partial saves, you should avoid yielding between setting values so the entire operation is atomic. You can grab some information, but nothing should interrupt your saves. Make sure that you handle errors while saving, they can cause your save to not be atomic.

In either case if you send off data to be saved before BindToClose is called, then it should be written. That is, unless Roblox has specifically added code to prevent allow code in BindToClose to save, but forget previously queued data to be saved. I highly doubt this is the case. Note that if the code that saves on BindToClose needs to grab data first, then it may get out of date cached values.

So to summarize, make your autosave code atomic, and you wont have any unusual issues.

If your save code can’t be make atomic, then add a counter to keep track of the number of active saves and a flag to prevent future saves. When BindToClose is called, set the flag and wait until the count of active saves is 0. BindToClose gives the function 30 seconds to save all data before the server is shutdown, which should be plenty of time.

Yeah, according to this:

-- Function to save player's data
local function savePlayerData(playerUserId)
	if sessionData[playerUserId] then
		local success, err = pcall(function()
			playerData:SetAsync(playerUserId, sessionData[playerUserId])
		end)
		if not success then
			error("Cannot save data for player!")
		end
	end
end
 
-- Function to save player data on exit
local function saveOnExit(player)
	local playerUserId = "Player_" .. player.UserId
	savePlayerData(playerUserId)
end
 
-- Function to periodically save player data
local function autoSave()
	while wait(AUTOSAVE_INTERVAL) do
		for playerUserId, data in pairs(sessionData) do
			savePlayerData(playerUserId)
		end
	end
end
 
-- Start running 'autoSave()' function in the background
spawn(autoSave)
 
-- Connect 'setupPlayerData()' function to 'PlayerAdded' event
game.Players.PlayerAdded:Connect(setupPlayerData)
 
-- Connect 'saveOnExit()' function to 'PlayerRemoving' event
game.Players.PlayerRemoving:Connect(saveOnExit)

Source: https://www.robloxdev.com/articles/Saving-Player-Data

They have saving in an auto save cycle and one player removing but they dont have anything checking whether one or the other is happening already. That’s currently how i’m doing it so i guess it’s fine?

And i’m saving cache’d data btw.

youre supposed to. making consistent getasync calls is useless when its literally the same data as if you were to keep it in a session table (CLEAR IT ON LEAVE TO PREVENT MEMORY LEAKS (i.e. non-garbagecollected players and save data))
and again use BindToClose because datastores might fail or take too long to complete when the last player leaves or the server is shut down causing them to lose all their new data after the last save and this will allow you to yield shutdown and keep the save script running until completion

That’s my whole issue.

What happens if another same kicks in right after calling one save? For example, the player leaving right after auto save cycle was called and it SetAsyncs one after the other? Will this have an error?

I still have yet to receive a clear answer.

I already answered that above. There is a “6 seconds between writes” rule. If you call 2 SetAsyncs right after each other, the second one will yield until 6 seconds have passed since the first write, and only then it performs the request.

Valid requests only throw errors if you are calling way too many of them at once and overflowing the throttle queue (pretty hard to do unless you’re doing something really stupid), or because datastores are temporarily unavailable.

Kind of why I wish using the quit button on the app turns up the “Are you sure you want to leave the game” window from the escape menu preferences. Having a game:playerQuit:connect() with a 1000 frame limit or something like that would be really helpful for last minute saves and actions.