Skip to content

fix(server): defer stopping a resource that is still running - #4403

Open
OneEarJoe5 wants to merge 1 commit into
citizenfx:masterfrom
OneEarJoe5:fix/defer-self-stop-restart
Open

OneEarJoe5 wants to merge 1 commit into
citizenfx:masterfrom
OneEarJoe5:fix/defer-self-stop-restart

Conversation

@OneEarJoe5

Copy link
Copy Markdown

Goal of this PR

A resource that stops or restarts itself crashes the server, e.g. ExecuteCommand('restart ' .. GetCurrentResourceName()). restart stops the resource right away, which destroys its runtime, and then the resource's own code that is still on the stack keeps running on freed state. That's #1421 (open since 2022, #1840 is a duplicate). It's not only JS: Lua does it too, and also the case where resource A calls an export of B and B restarts A.

STOP_RESOURCE already refuses to stop the calling resource, but the stop / restart / ensure commands that ExecuteCommand reaches don't check anything.

How is this PR achieving the goal

  • Every resource gets a counter that goes up on OnActivate and down on OnDeactivate, so it tells how often the resource is on the script runtime stack right now (same events ResourceTimeWarnings and others already use).
  • stop, restart and ensure (only the stop+start part) check that counter. If the resource is running, the command is queued with gscomms_execute_callback_on_main_thread(..., true) and runs on the next server tick, after the resource's code has returned. So a self restart now just works instead of crashing.
  • The queued command runs with the principal of the resource that issued it (like ExecuteCommand pushes resource.<name>), so ACE checks stay the same. A resource without command.restart still gets Access denied and nothing is queued.
  • If the resource were somehow still running when the queued command runs, it prints a warning instead of queueing again, so it can't loop.
  • Nothing changes when the resource isn't running: console commands, txAdmin and scripts stopping or restarting other resources still happen right away, like before.

This PR applies to the following area(s)

Server, ScRT: Lua, ScRT: JS

Successfully tested on

Platforms: Windows (server)

Server built from current master (5412ed8), with and without this change. Test resources: a JS resource that runs restart / ensure / stop on itself from different places, a Lua resource doing the same, a Lua resource with an export that restarts the caller, and one that only gets restarted from outside. Each case in a fresh server.

<cmd> <own resource> from before after
JS setTimeout / setTick / node setImmediate / node setTimeout crash (all of them, for restart and ensure) restarts cleanly
JS event or command handler, triggered from Lua crash restarts cleanly
JS calls a Lua export that restarts the JS resource crash restarts cleanly (kept doing it every second, 18 restarts in a row)
Lua SetTimeout crash restarts cleanly
stop instead of restart, same places crash, or "Error calling system ... function: null" and a half dead resource stops cleanly

Also checked:

  • restart / stop / ensure / ensure of another resource from a script: still synchronous, GetResourceState right after is started / stopped / started / started like before.
  • the same commands typed into the server console (which is also how txAdmin does it), including restarting the JS test resource itself from the console: run right away like before.
  • a resource without the ACE for restart trying to restart itself: Access denied for command restart., resource keeps running, nothing queued.
  • the "still running" safety net: with a test-only hack that never lets the counter of one resource go back to 0, a self restart prints Couldn't restart resource ..., it's still running. once, nothing is queued again, and the server keeps going (console commands right after still work, no hitch warnings).
  • No new compiler warnings in the changed code.

The "before" column looks the same on the official Linux artifact 38621 (built from the same commit): every restart / ensure case crashes there too, and stop either crashes or leaves the resource half dead.

Not tested: a Linux build with this change (the logic doesn't touch anything platform specific).

Checklist

  • Code compiles and has been tested successfully.
  • Code explains itself well and/or is documented.
  • My commit message explains what the changes do and what they are for.
  • No extra compilation warnings are added by these changes.

Fixes issues

fixes #1421

`ExecuteCommand('restart ' .. GetCurrentResourceName())` (or stop /
ensure) tears down the runtime while the resource's own code is still
on the stack, which crashes the server once that code continues. This
happens for Lua and JS, from timers, ticks, events, commands and from
another resource that the resource called into.

Keep track of how often each resource is on the script runtime stack
(OnActivate / OnDeactivate) and, while it is, run stop, restart and
ensure for it on the next server tick instead, with the same principal
that issued the command. Everything else, including stopping or
restarting other resources and commands from the console, still runs
right away.

Signed-off-by: OneEarJoe5 <212275319+OneEarJoe5@users.noreply.github.com>
@github-actions github-actions Bot added ScRT: JS Issues/PRs related to the JavaScript scripting runtime ScRT: Lua Issues/PRs related to the Lua scripting runtime labels Nov 14, 2026
@github-actions github-actions Bot added the invalid Requires changes before it's considered valid and can be (re)triaged label Nov 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

invalid Requires changes before it's considered valid and can be (re)triaged ScRT: JS Issues/PRs related to the JavaScript scripting runtime ScRT: Lua Issues/PRs related to the Lua scripting runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Restarting current resource within itself results in server crash

1 participant