Files
esx_core/[core]/cron/server/main.lua
T
_Not_ 4dd4c9c8ca Merge pull request #1839 from seltonmt012/fix/cron-callback-guard
fix(cron): keep the scheduler alive when a job errors
2026-08-14 19:41:00 -05:00

87 lines
2.4 KiB
Lua

---@class CronJob
---@field h number
---@field m number
---@field cb function|table
---@type CronJob[]
local cronJobs = {}
---@type number|false
local lastTimestamp = false
---@param h number
---@param m number
---@param cb function|table
function RunAt(h, m, cb)
cronJobs[#cronJobs + 1] = {
h = h,
m = m,
cb = cb,
}
end
---@return number
function GetUnixTimestamp()
return os.time()
end
---@param timestamp number
function OnTime(timestamp)
local function scheduledAt(job, dayOffset)
return os.time({
hour = job.h,
min = job.m,
sec = 0, -- Assuming tasks run at the start of the minute
day = os.date("%d", timestamp) + dayOffset,
month = os.date("%m", timestamp),
year = os.date("%Y", timestamp),
})
end
for i = 1, #cronJobs, 1 do
local scheduledTimestamp = scheduledAt(cronJobs[i], 0)
if scheduledTimestamp > timestamp then
scheduledTimestamp = scheduledAt(cronJobs[i], -1)
end
if not lastTimestamp or lastTimestamp < scheduledTimestamp then
local d = os.date('*t', scheduledTimestamp).wday
if not pcall(cronJobs[i].cb, d, cronJobs[i].h, cronJobs[i].m) then
print(("[^1ERROR^7] cron job at ^5%02d:%02d^7 errored, skipping it"):format(cronJobs[i].h, cronJobs[i].m))
end
end
end
end
---@return nil
function Tick()
local timestamp = GetUnixTimestamp()
if not lastTimestamp or os.date("%M", timestamp) ~= os.date("%M", lastTimestamp) then
OnTime(timestamp)
lastTimestamp = timestamp
end
SetTimeout(60000, Tick)
end
lastTimestamp = GetUnixTimestamp()
Tick()
---@param h number
---@param m number
---@param cb function|table
AddEventHandler("cron:runAt", function(h, m, cb)
local invokingResource = GetInvokingResource() or "Unknown"
local typeH = type(h)
local typeM = type(m)
local typeCb = type(cb)
assert(typeH == "number", ("Expected number for h, got %s. Invoking Resource: '%s'"):format(typeH, invokingResource))
assert(typeM == "number", ("Expected number for m, got %s. Invoking Resource: '%s'"):format(typeM, invokingResource))
assert(typeCb == "function" or (typeCb == "table" and type(getmetatable(cb)?.__call) == "function"), ("Expected function for cb, got %s. Invoking Resource: '%s'"):format(typeCb, invokingResource))
RunAt(h, m, cb)
end)