Enhance request pipeline with safe operation dispatch, support for paused server states, and bridge status updates.
This commit is contained in:
@@ -1,8 +1,18 @@
|
||||
---@diagnostic disable: need-check-nil
|
||||
|
||||
-- Registration deferred to register(), called from RDB_Bootstrap's
|
||||
-- onServerStarted, see RDB_OpWorldGetStats.lua.
|
||||
|
||||
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()
|
||||
RDB_OpRegistry.register("get_bridge_status", {
|
||||
handler = function(_)
|
||||
@@ -13,6 +23,7 @@ function RDB_OpBridgeStatus.register()
|
||||
version = RDB_Constants.PROTOCOL_VERSION,
|
||||
bootedAt = bootedAt,
|
||||
uptimeSeconds = (now - bootedAt) / 1000,
|
||||
paused = isPaused(),
|
||||
rateLimit = RDB_Security.getRateLimitStatus(),
|
||||
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 })
|
||||
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
|
||||
-- changeoption command strips every literal double-quote character from
|
||||
-- every argument (see RESEARCH_LOG.md), so a client can't send raw JSON --
|
||||
@@ -109,7 +122,7 @@ local function processRequest(rawBase64)
|
||||
return
|
||||
end
|
||||
|
||||
local dispatchOk, dataOrCode, message = RDB_OpRegistry.dispatch(op, args)
|
||||
local dispatchOk, dataOrCode, message = safeDispatch(op, args)
|
||||
local response
|
||||
if dispatchOk then
|
||||
response = buildOkResponse(id, dataOrCode)
|
||||
@@ -148,14 +161,28 @@ end
|
||||
|
||||
local initialized = false
|
||||
|
||||
-- Guarded: Events.OnServerStarted fires more than once per boot (see
|
||||
-- RESEARCH_LOG.md), and Events.OnTick.Add does not deduplicate identical
|
||||
-- listeners, so calling this twice would otherwise double-register onTick.
|
||||
-- Guarded: Events.OnServerStarted fires more than once per boot,
|
||||
-- and Events.OnTick.Add does not deduplicate identical listeners,
|
||||
-- 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()
|
||||
if initialized then
|
||||
return
|
||||
end
|
||||
|
||||
initialized = true
|
||||
lastSeenRaw = RDB_OptionsRegistry.get(RDB_Constants.OPT.REQUEST)
|
||||
|
||||
Events.OnTick.Add(onTick)
|
||||
Events.OnTickEvenPaused.Add(onTick)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user