Infinite Yield On DataStore Async Calls

Is it possible to get an infinite yield using DataStore’s async calls?

such as

GetSortedAsync
SetAsync

because my leaderboard script just launches into “UPDATING” mode but never gets out of it. The issue seems to have very rarely across many servers, though

No, it’s not possible for any HTTP call to yield infinitely. All HTTP requests have a timeout duration. If anything, after a few seconds the HTTP call will return status 408 Request Timeout

1 Like

I’d assume that’s an issue with your implementation and not the DataStore calls themselves. DataStore methods internally are Http calls and those have timeouts. Have you investigated the cause via console or review of your code?

The issue may be to do with you current code. It would be nice if we were able to see it. Do you get any other errors expect from an infinite yield?

--constants
local PLAYERS = game:GetService("Players")

local TOP_LEVELS_ODS = game:GetService("DataStoreService"):GetOrderedDataStore("TopLevels")
local WAVES_SURVIVED_ODS = game:GetService("DataStoreService"):GetOrderedDataStore("WavesSurvived")
local DONATED_ROBUX_ODS = game:GetService("DataStoreService"):GetOrderedDataStore("DonatedRobux")
local TOP_KILLS_ODS = game:GetService("DataStoreService"):GetOrderedDataStore("TopKills")

local WEEKLY_TOP_KILLS_ODS;

local RE = game.ReplicatedStorage:WaitForChild("RE")
local DATA_MODULE = require(script.Parent.Data)
local ROLE_TAGS_MODULE = require(game.ReplicatedStorage.Modules["Role Tags"])

local GUI = game.ReplicatedStorage:WaitForChild("GUI")
local GLOBAL_LIST_ITEM = GUI:WaitForChild("GlobalListItem")
local WEEKLY_LIST_ITEM = GUI:WaitForChild("WeeklyListItem")

local LOBBY = workspace:WaitForChild("Lobby")

--settings
local UPDATE_CYCLE = 45 --refreshes leaderbord every 10 seconds
local EXCLUDE_ADMINS_AND_ABOVE = false

local SECONDS_IN_WEEK = 60 * 60 * 24 * 7 --60 * 60 * 24 * 7
local WEEK_ONE_START_TIME = 1552262399 --3/10/19 11:59:59 PM UTC https://www.epochconverter.com/

local MAX_WEEKLY_PLACES = 50
--print(SECONDS_IN_WEEK % ((WEEK_ONE_START_TIME + SECONDS_IN_WEEK)- WEEK_ONE_START_TIME))

--body
local lastUpdated = 0
local updating = false
local currentWeek = nil

local caches = {
	global = {
		level = {},
		wavesSurvived = {},
		donatedRobux = {},
		kills = {}	
	},
	weekly = {
		kills = {}
	}
}

local LeaderBoardService = {}
LeaderBoardService.__index = LeaderBoardService

--private static functions (bc isnt needed externally)

function FormatCountdownDHMS(seconds)
	return string.format("%d:%02d:%02d:%02d", (seconds/86400)%7, (seconds/3600)%24, (seconds/60)%60, seconds%60)
end

--print(FormatCountdownDHMS((60 * 60 * 24 * 8)  - 67 ))
--[[
print(string.format("%05d", 23.23))]]

function FormatPlace(place)
	if place == 1 then
		return "st"
	elseif place == 2 then
		return "nd"
	elseif place == 3 then
		return "rd"
	else
		return "th"
	end
end

function AbbreviateNumber(n)
	if n > 1000 then
		n = (n/1000)
		return (math.floor(n/.1+0.5)*.1) .. "K"
	elseif n > 1000000 then
		n = (n/1000000)
		return (math.floor(n/.1+0.5)*.1).. "M"
	else
		return n .. ""
	end
end

function UpdateODS(ODS, valueDir)
	for _, player in pairs(PLAYERS:GetPlayers()) do
		local plyrData = DATA_MODULE.GetPlayerData(player)
		if plyrData then
			local value
			local adminOrAbove = ROLE_TAGS_MODULE.Check(player, "ADM")
			if adminOrAbove and EXCLUDE_ADMINS_AND_ABOVE then
				value = 0
			else
				value = plyrData
				for i = 1, #valueDir do
					value = value[valueDir[i]]
				end
			end
			if typeof(value) == "number" then --just in case
				local success, msg = pcall(function()
					ODS:SetAsync(player.UserId, value)
				end)
			end
		end
	end
end

function UpdateCache(ODS, cacheType, keyName, pageSize)
	if ODS and cacheType[keyName] then
		local newCache = {}
		--local newInGameTopCache = {}	
		local success, pages = pcall(function()
			return ODS:GetSortedAsync(false, pageSize)
		end)
		if success and pages then
			local currentPage = pages:GetCurrentPage()
			for it, item in pairs(currentPage) do
				local userID = item.key		
				--[[for _, player in pairs(PLAYERS:GetPlayers()) do
					if tostring(player.UserId) == tostring(userID) then
						newInGameTopCache[tostring(player.UserId)] = it
					end
				end]]
				local success, name = pcall(
					function()
						return PLAYERS:GetNameFromUserIdAsync(userID)
					end
				)
				if success and name then
					local val = item.value
					if val > 0 then
						newCache[it] = {
							userID = userID,
							name = name,
							image = "http://www.roblox.com/Thumbs/Avatar.ashx?x=100&y=100&Format=Png&username=" .. name,
							[keyName] = val,
						}
					end
				end
			end
			cacheType[keyName] = newCache
			--inGameTopPlayersCache[cacheName] = newInGameTopCache
		end
	end
end

--static methods (exposed externally)
function LeaderBoardService.GetWeeklyPlacePrize(place)
	
	local startingPrize = 40000
	local manipPrize = 35000
	
	local scale = (MAX_WEEKLY_PLACES - math.clamp(place, 2, MAX_WEEKLY_PLACES)) / (MAX_WEEKLY_PLACES - 2)
	
	local bonusPrize = place == 1 and 25000 or 0
	
	return math.max(0, math.floor((scale * manipPrize) + 0.5) + startingPrize + bonusPrize)
	
end

function LeaderBoardService.GetCurrentWeek()
	return math.floor((os.time() - WEEK_ONE_START_TIME)/SECONDS_IN_WEEK)
end

function LeaderBoardService.UpdateGlobal()
	UpdateODS(TOP_LEVELS_ODS, {"Stats", "Level"})
	UpdateODS(WAVES_SURVIVED_ODS, {"Stats", "WavesSurvived"})
	UpdateODS(DONATED_ROBUX_ODS, {"Stats", "DonatedRobux"})
	UpdateODS(TOP_KILLS_ODS, {"Stats", "TotalKills"})
		
	UpdateCache(TOP_LEVELS_ODS, caches.global, "level", 10)
	UpdateCache(WAVES_SURVIVED_ODS, caches.global, "wavesSurvived", 10)
	UpdateCache(DONATED_ROBUX_ODS, caches.global, "donatedRobux", 10)
	UpdateCache(TOP_KILLS_ODS, caches.global, "kills", 10)
	
	--visual
	--print("[GLOBAL]")		
	--PrintTopPlayers(caches.global)
	for topType, tops in pairs(caches.global) do
		--print(string.upper(topType))
		if LOBBY.BulletinBoard.SurfaceGuis.Global.Slides:FindFirstChild(topType) then
			--get slide
			local slide = LOBBY.BulletinBoard.SurfaceGuis.Global.Slides[topType]
			--reset image
			for _, image in pairs(slide.Places:GetChildren()) do
				image.ProfileImage.Image = ""
			end
			for _, item in pairs(slide.List:GetChildren()) do
				item:Destroy()
			end
			local y = 0 
			for place, playerInfo in pairs(tops) do
				if slide.Places:FindFirstChild(place) then
					slide.Places[place].ProfileImage.Image = playerInfo.image
				end
				local item = GLOBAL_LIST_ITEM:clone()
				item.Place.Text = place .. FormatPlace(place)
				item.User.Text = playerInfo.name
				item.Type.Text = AbbreviateNumber(playerInfo[topType])
				item.Position = UDim2.new(0, 0, 0, y)
				item.Parent = slide.List
				y = y + 70
				--print(place, playerInfo.name, AbbreviateNumber(playerInfo[topType]))
			end
			--adjust scroll
			slide.List.CanvasSize = UDim2.new(0, 0, 0, 265 + math.max(1, y - 265)) 
		end
	end
end

function LeaderBoardService.CheckTopPlace(ODSName, player, pageSize)
	local ODS = game:GetService("DataStoreService"):GetOrderedDataStore(ODSName)
	local success, pages = pcall(function()
		return ODS:GetSortedAsync(false, pageSize)
	end)
	if success and pages then
		local currentPage = pages:GetCurrentPage()
		for place, item in pairs(currentPage) do
			local userID = item.key		
			if item.value > 0 and tostring(player.UserId) == tostring(userID) then
				return true, place, item.value
			end
		end
		return true, nil, nil
	else
		player:Kick("Error loading data, please rejoin")
	end
end

--regular methods
function LeaderBoardService:UpdateWeekly()
	UpdateODS(WEEKLY_TOP_KILLS_ODS, {"WeeklyStats", "Kills"})
	UpdateCache(WEEKLY_TOP_KILLS_ODS, caches.weekly, "kills", MAX_WEEKLY_PLACES)
	--reset image
	for _, image in pairs(LOBBY.BulletinBoard.SurfaceGuis.Weekly.Places:GetChildren()) do
		image.ProfileImage.Image = ""
	end
	for _, item in pairs(LOBBY.BulletinBoard.SurfaceGuis.Weekly.Main.List:GetChildren()) do
		item:Destroy()
	end
	local y = 0
	for place, playerInfo in pairs(caches.weekly.kills) do
		if LOBBY.BulletinBoard.SurfaceGuis.Weekly.Places:FindFirstChild(place) then
			LOBBY.BulletinBoard.SurfaceGuis.Weekly.Places[place].ProfileImage.Image = playerInfo.image
		end
		local item = WEEKLY_LIST_ITEM:clone()
		item.Place.Text = place
		item.User.Text = playerInfo.name
		item.Type.Text = AbbreviateNumber(playerInfo.kills)
		item.PrizeValue.Text = AbbreviateNumber(self.GetWeeklyPlacePrize(place))
		item.Position = UDim2.new(0, 190, 0, y)
		item.Parent = LOBBY.BulletinBoard.SurfaceGuis.Weekly.Main.List
		y = y + 60
	end
	LOBBY.BulletinBoard.SurfaceGuis.Weekly.Main.List.CanvasSize = UDim2.new(0, 0, 0, 375 + math.max(1, y - 375)) 
	--check for those in game
	for _, player in pairs(PLAYERS:GetPlayers()) do
		local playerData = DATA_MODULE.GetPlayerData(player)
		if playerData and player:FindFirstChild("TempStats") and player.TempStats.Loaded.Value == true then
			local playerPlace, playerPrize, playerKills = nil, nil, playerData.WeeklyStats.Kills
			for place, playerInfo in pairs(caches.weekly.kills) do
				if tostring(player.UserId) == tostring(playerInfo.userID) then
					playerPlace, playerPrize = place, self.GetWeeklyPlacePrize(place)
				end
			end
			RE:FireClient(player, "UpdateWeeklyStats", {Place = playerPlace, Prize = playerPrize, Kills = playerKills})
		end
	end
end

function LeaderBoardService:ManageWeek()
	local uCurrentWeek = self.GetCurrentWeek()
	if not currentWeek or currentWeek ~= uCurrentWeek then
		--update week once more
		--UpdateWeekly()
		print("CURRENT WEEK - " .. uCurrentWeek)
		--award those in game while week changed
		local success, pages = pcall(function()
			return WEEKLY_TOP_KILLS_ODS:GetSortedAsync(false, MAX_WEEKLY_PLACES)
		end)
		if success and pages then
			local currentPage = pages:GetCurrentPage()
			for place, item in pairs(currentPage) do
				local userID = item.key		
				for _, player in pairs(PLAYERS:GetPlayers()) do
					local playerData = DATA_MODULE.GetPlayerData(player)
					if playerData and player:FindFirstChild("TempStats") and player.TempStats.Loaded.Value == true then
						if item.value > 0 and tostring(player.UserId) == tostring(userID) then
							local prize = self.GetWeeklyPlacePrize(place)
							DATA_MODULE.EditData(player, {
								{Type = "AddCredits", Value = prize}
							})
							RE:FireClient(player, "WeeklyWin", {Week = currentWeek, Place = place, Prize = tostring(prize)})
						end
					end
				end
			end
		end
		--set new ods
		WEEKLY_TOP_KILLS_ODS = game:GetService("DataStoreService"):GetOrderedDataStore("Week" .. uCurrentWeek .. "TopKills")
		--clear cache
		caches.weekly.kills = {}
		--set last week
		currentWeek = uCurrentWeek
	end
	--clear weekly player data, if oudated week
	--this happens its own code when the player rejoins anyways
	for _, player in pairs(PLAYERS:GetPlayers()) do
		local plyrData = DATA_MODULE.GetPlayerData(player)
		if player:FindFirstChild("TempStats") and player.TempStats:FindFirstChild("Loaded") and player.TempStats.Loaded.Value == true and plyrData and plyrData.WeeklyStats.Week ~= uCurrentWeek then
			DATA_MODULE.EditData(player, {
				{Type = "ResetWeeklyStats", Week = uCurrentWeek},
			})
		end
	end
	--
	local nextWeekEndTime = SECONDS_IN_WEEK + ((currentWeek * SECONDS_IN_WEEK) + WEEK_ONE_START_TIME)
	LOBBY.BulletinBoard.SurfaceGuis.Weekly.Info.Time.Time.Text = FormatCountdownDHMS(math.max(0, nextWeekEndTime - os.time()))
end

function LeaderBoardService:Update()
	self:ManageWeek()
	--update last updated
	if not self.updating and ((tick() - lastUpdated) > UPDATE_CYCLE) then
		delay(0, function()
			--if needed, then false
			self.updating = true
			self.UpdateGlobal()
			self:UpdateWeekly()
			lastUpdated = tick()
			self.updating = false
		end)
	end
	local seconds = math.floor(tick() - lastUpdated)
	LOBBY.BulletinBoard.SurfaceGuis.Update.Time.Text = self.updating == true and "UPDATING" or string.format("%02d min %02d sec",(seconds/60)%60, seconds%60)
end

--start it off
function LeaderBoardService:Start()
	print("LeaderBoardService started...")
	self:ManageWeek()
end

return LeaderBoardService
1 Like

What errors or outputs are you getting for this?

None. I checked the servers that had the issue.

Absolutely none.

I thought it might be my main look firing the :Update method that stopped, but if it did the whole game would be broken, which is was not.