Datastore does not consistently save on PlayerRemoving or BindToClose

local function saveData(plr)
    local tableToSave = {
		plr.leaderstats.Wins.Value,
		plr.leaderstats.Streaks.Value,
		plr.Values.Coins.Value
	}

	local success, err 
	local n = 0 
	
	while not success do
		if n > 0 then
			print("Retrying")
			wait(1)
		end
		success, err = pcall(function()
			dataStore:SetAsync(plr.UserId, tableToSave)
		end)  
		
		n += 1
	end
	
	if success then
		print("Data has been saved!")
	end
end

The function is consistently called when the player leaves or when the server shuts down, but sometimes the data is failing to save and does not print any outputs compared to when it succeeds, which will print “Data has been saved!”. Interestingly whenever it fails, it does not show the disconnecting message that usually prints when I stop testing in Studio.

Why not test this in a real game?

Is there a difference in data saving between studio testing and in game?

If you test it in studio, most likely these events wouldn’t call, or data store cannot respond.

If you test it in a game, it’s more accurate. I got this issue when I first try using data store, and the time it worked when I test it in a real game.

I believe this is a common misunderstanding.

I’ve never had issues with Studio on a case where I wasn’t doing something wrong. The only big difference between studio and client is that on the actual game, requests to datastores should error less commonly and that requests are way quicker. I believe that’s because Roblox servers have more direct access to datastores.

With that said, I believe this is an issue on this person’s BindToClose, so showing it would be useful.

1 Like

The BindToClose and PlayerRemoving event fires as normal in studio, and the function is called without erroring, whilst the issue occurs at the loop/pcall, seemingly yielding. However after I have tested in game, the data does save for some reason.

This is most likely a problem with your BindToClose, like I said. Your BindToClose must yield until everyone’s data is saved.

game.Players.PlayerRemoving:Connect(function(plr)
	local success, err  = pcall(function()
		wait() -- delay for value changes
		saveData(plr)
	end)
end)

game:BindToClose(function()
	print("Server Closing!")
	for _, plr in pairs(game.Players:GetPlayers()) do
		local success, err  = pcall(function()
			coroutine.wrap(saveData)(plr)
		end)
	end
end)

This is the connection script.

Yep, like I said, BindToClose isn’t set up properly.

Something like this instead should work.
Btw task.spawn is just a better coroutine.wrap.

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

game:BindToClose(function()
    local leftToSave = 0
    for _, player in ipairs(Players:GetPlayers()) do
        task.spawn(function()
            leftToSave += 1
            saveData(player)
            leftToSave -= 1
        end)
    end

    while leftToSave > 0 do
        RunService.Heartbeat:Wait() -- you can also use task.wait(), but don't use wait() 😡😡
    end
end)

BindToClose handlers should yield until whatever they’re doing is finished, that’s why they exist, to prepare for a server being shutdown and end everything properly. The server only actually shuts down once all :BindToClose calls are finished, or after 30 seconds.

Another tip I have for you, keep a table with all the players that have data loaded, and only save the data from the players inside that table.
When you begin to save their data, remove that player from that table.

This would help with not saving empty data in case a player ends up not loading properly, or fast enough.

About that, this post should help with that in specific.

2 Likes

I appreciate your support, the issue is now resolved.

I believe testing in a real game is more better. Because it always happen when I test in studio.