Enhance request pipeline with safe operation dispatch, support for paused server states, and bridge status updates.
This commit is contained in:
@@ -1,18 +1,29 @@
|
|||||||
|
---@diagnostic disable: need-check-nil
|
||||||
|
|
||||||
-- Registration deferred to register(), called from RDB_Bootstrap's
|
-- Registration deferred to register(), called from RDB_Bootstrap's
|
||||||
-- onServerStarted, see RDB_OpWorldGetStats.lua.
|
-- onServerStarted, see RDB_OpWorldGetStats.lua.
|
||||||
|
|
||||||
RDB_OpBridgeStatus = {}
|
RDB_OpBridgeStatus = {}
|
||||||
|
|
||||||
|
-- Mirrors zombie.GameTime.isGamePaused()'s server-side branch exactly
|
||||||
|
-- (decompiled: `GameServer.Players.isEmpty() && ServerOptions.instance.pauseEmpty.getValue()`),
|
||||||
|
-- true precisely when RDB_RequestPipeline would be relying on Events.OnTickEvenPaused
|
||||||
|
-- instead of Events.OnTick to even see this request.
|
||||||
|
local function isPaused()
|
||||||
|
return ServerOptions.instance:getBoolean("PauseEmpty") and getOnlinePlayers():size() == 0
|
||||||
|
end
|
||||||
|
|
||||||
function RDB_OpBridgeStatus.register()
|
function RDB_OpBridgeStatus.register()
|
||||||
RDB_OpRegistry.register("get_bridge_status", {
|
RDB_OpRegistry.register("get_bridge_status", {
|
||||||
handler = function(_)
|
handler = function(_)
|
||||||
local bootedAt = RDB_Bootstrap.getBootTimestamp()
|
local bootedAt = RDB_Bootstrap.getBootTimestamp()
|
||||||
local now = getTimestampMs()
|
local now = getTimestampMs()
|
||||||
|
|
||||||
return true, {
|
return true, {
|
||||||
version = RDB_Constants.PROTOCOL_VERSION,
|
version = RDB_Constants.PROTOCOL_VERSION,
|
||||||
bootedAt = bootedAt,
|
bootedAt = bootedAt,
|
||||||
uptimeSeconds = (now - bootedAt) / 1000,
|
uptimeSeconds = (now - bootedAt) / 1000,
|
||||||
|
paused = isPaused(),
|
||||||
rateLimit = RDB_Security.getRateLimitStatus(),
|
rateLimit = RDB_Security.getRateLimitStatus(),
|
||||||
config = RDB_Config.getAll(),
|
config = RDB_Config.getAll(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,19 @@ local function audit(id, op, ok, code)
|
|||||||
RDB_AuditLog.record({ id = id, op = op, ok = ok, code = code })
|
RDB_AuditLog.record({ id = id, op = op, ok = ok, code = code })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- If an op handler throws (a bug, a bad deploy, a version mismatch between
|
||||||
|
-- files), surface it as a fast INTERNAL_ERROR response instead of silently
|
||||||
|
-- swallowing it. The raw Lua error is logged server-side only, never
|
||||||
|
-- sent to the client.
|
||||||
|
local function safeDispatch(op, args)
|
||||||
|
local ok, a, b, c = pcall(RDB_OpRegistry.dispatch, op, args)
|
||||||
|
if not ok then
|
||||||
|
RDB_Log.error("op '" .. tostring(op) .. "' handler threw: " .. tostring(a))
|
||||||
|
return false, RDB_Constants.ERROR_CODES.INTERNAL_ERROR, "Unhandled error in operation handler."
|
||||||
|
end
|
||||||
|
return a, b, c
|
||||||
|
end
|
||||||
|
|
||||||
-- Handles one raw request string end to end. raw is base64: RCON's
|
-- Handles one raw request string end to end. raw is base64: RCON's
|
||||||
-- changeoption command strips every literal double-quote character from
|
-- changeoption command strips every literal double-quote character from
|
||||||
-- every argument (see RESEARCH_LOG.md), so a client can't send raw JSON --
|
-- every argument (see RESEARCH_LOG.md), so a client can't send raw JSON --
|
||||||
@@ -109,7 +122,7 @@ local function processRequest(rawBase64)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local dispatchOk, dataOrCode, message = RDB_OpRegistry.dispatch(op, args)
|
local dispatchOk, dataOrCode, message = safeDispatch(op, args)
|
||||||
local response
|
local response
|
||||||
if dispatchOk then
|
if dispatchOk then
|
||||||
response = buildOkResponse(id, dataOrCode)
|
response = buildOkResponse(id, dataOrCode)
|
||||||
@@ -148,14 +161,28 @@ end
|
|||||||
|
|
||||||
local initialized = false
|
local initialized = false
|
||||||
|
|
||||||
-- Guarded: Events.OnServerStarted fires more than once per boot (see
|
-- Guarded: Events.OnServerStarted fires more than once per boot,
|
||||||
-- RESEARCH_LOG.md), and Events.OnTick.Add does not deduplicate identical
|
-- and Events.OnTick.Add does not deduplicate identical listeners,
|
||||||
-- listeners, so calling this twice would otherwise double-register onTick.
|
-- so calling this twice would otherwise double-register onTick.
|
||||||
|
--
|
||||||
|
-- Registered on BOTH OnTick and OnTickEvenPaused: decompiling GameWindow's
|
||||||
|
-- main loop shows these two are mutually exclusive per real engine tick;
|
||||||
|
-- normal ticks fire OnTick (via IngameState's update path), while a tick
|
||||||
|
-- where the server is paused (PauseEmpty=true and no players connected;
|
||||||
|
-- see zombie.GameTime.isGamePaused) fires OnTickEvenPaused instead, never
|
||||||
|
-- both. Without the second registration, the entire bridge (every op, not
|
||||||
|
-- just one) goes completely inert on any server running PauseEmpty=true
|
||||||
|
-- whenever it's empty: no event ever calls onTick, so a request just sits
|
||||||
|
-- in RconDataBridge_Request forever with no response and no error, and a
|
||||||
|
-- client sees a silent timeout with nothing to diagnose.
|
||||||
function RDB_RequestPipeline.init()
|
function RDB_RequestPipeline.init()
|
||||||
if initialized then
|
if initialized then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
initialized = true
|
initialized = true
|
||||||
lastSeenRaw = RDB_OptionsRegistry.get(RDB_Constants.OPT.REQUEST)
|
lastSeenRaw = RDB_OptionsRegistry.get(RDB_Constants.OPT.REQUEST)
|
||||||
|
|
||||||
Events.OnTick.Add(onTick)
|
Events.OnTick.Add(onTick)
|
||||||
|
Events.OnTickEvenPaused.Add(onTick)
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user