I’ve done a lot of debugging and for some reason in my module script it only gets to my “got through this” print. This means that my SetAsync pcall is causing something. I just don’t know what. It actually also prints the savedData after that print but thats basically just saying that this line is causing the problem:
Problem:
Saver:SetAsync(tostring(plr.UserId),savedData)
Module Script:
local DDS = game:GetService("DataStoreService")
local Saver = DDS:GetDataStore("SaveLeaderstatsV2")
local rp = game:GetService("ReplicatedStorage")
local module = {}
local Devs = {
[3653923696] = true,
[134989192] = true
}
local Ranks = {
[3] = {
["[Honored One]"] = 50,
["[Special Grade]"] = 20,
["[1st Grade]"] = 15
},
[2] = {
["[Semi-1st Grade]"] = 12,
["[2nd Grade]"] = 9
},
[1] = {
["[3rd Grade]"] = 6,
["[4th Grade]"] = 4
},
[0] = {
["[5th Grade]"] = 2,
["[Homeless]"] = 0
}
}
function module.SaveRank(plr)
print("got to the start")
local savedData = {}
for _,v in pairs(plr.leaderstats:GetChildren()) do
savedData[v.Name] = v.Value
end
print("got through this")
local Yes, errorMessage = pcall(function()
print(savedData)
Saver:SetAsync(tostring(plr.UserId),savedData)
end)
print("got to this part")
if not Yes then
error(errorMessage) -- womp womp if this happens to you
end
print("got to the end")
end
Server Script:
local rankModule = require(script.InfoModule)
local players = game:GetService("Players")
players.PlayerAdded:Connect(function(plr)
rankModule.checkRank(plr)
end)
players.PlayerRemoving:Connect(function(plr)
warn("hi")
local success, errorMsg = pcall(function()
rankModule.SaveRank(plr)
end)
if not success then
print("Error occurred: " .. errorMsg)
end
warn("after")
end)
--game:BindToClose(function() -- happens when the game shuts down
-- for _,v in pairs(players:GetPlayers()) do
-- rankModule.SaveRank(v)
-- end
--end)
There is more code to the module script but I just thought it was too overwhelming and it’s not actually useful in fixing this problem so why would I share it and give you guys a harder time. Also it doesn’t print any errors in the output.
:SetAsync will yield because it has to make a request to an external server to save the data which won’t happen instantly. Because it’s yielding, the server then shuts down before the request can complete. SetAsync yielding is expected behaviour and it is what you want to happen.
The solution here isn’t to move your :SetAsync call outside of a pcall (pcall doesn’t actually create a new thread or anything, it just catches errors, so it will yield). It’s to call game:BindToClose and then save the data for each player inside of the function you pass to it, so the server shutdown doesn’t happen until you finish saving data.
If you un-comment this part, you should get your expected result and should also be able to see all of the prints.
Thank you for the explanation, but I believe the problem still persists.
SetAsync is known to yield. That part is fine.
However, the behavior of SetAsync suddenly changes when it’s moved outside of the pcall() block. Originally, it yields indefinitely, but outside of the pcall statement it does not yield.
@MonsterTrident have you confirmed that :BindToClose() actually fixes the problem?
I’ve actually copied your code in a test place now and seems to work fine both with :BindToClose enabled and disabled. Are you sure there’s no other moving parts that is messing up the script?
Your results can vary depending on things like Roblox’s server speed and (I’d assume) server stress, but regardless, the problem that op is facing is that the server is being shut down prematurely, thus all of the game’s threads will be terminated (including the thread created by players.PlayerRemoving) and the server will be shut down before all data can save.
The only way to delay the server’s shutdown is to call game:BindToClose and in the function passed to it, yield until all data is saved.
You can try this yourself, a very artificial example:
local players = game:GetService('Players')
players.PlayerRemoving:Connect(function(player)
print('PlayerRemoving called') -- will run
task.wait(3)
print('This part was reached') -- will not run
end)
however if you yield inside of game:BindToClose, for, say, 4 seconds, it will run:
local players = game:GetService('Players')
players.PlayerRemoving:Connect(function(player)
print('PlayerRemoving called') -- will run
task.wait(3)
print('This part was reached') -- now, because we called game:BindToClose, will run
end)
game:BindToClose(function()
task.wait(4)
end)