Repository navigation
Let server resources hold a stop until they have saved, in contract 7 - #55
Merged
Merged
Conversation
…a limit the owner sets, in contract 7
This was referenced Nov 11, 2026
# Conflicts: # README.md # crates/fake-fxserver/src/main.rs # crates/fake-fxserver/tests/runtime.rs # crates/observer-domain/src/audit.rs # crates/observer-fxserver/src/bridge.rs # crates/observer-fxserver/src/supervisor.rs # crates/observer-store/src/migrations.rs # crates/observer-web/src/error.rs # resource/CONTRACT.md # resource/observer/server.lua # resource/observer_example/server.lua # web/src/api/generated/AuditAction.ts # web/src/api/generated/ErrorCode.ts # web/src/audit/details.ts # web/src/shell/ServerControls.test.tsx # web/src/shell/ServerControls.tsx
…od of its own, for the launch to stay within clippy's length
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Before a stop, Observer raised
observer:shuttingDown, waited a fixed 5 seconds, then sentquit. A resource that saves for longer, such as inventories, vehicles or its own data, was cut off halfway through its saves. Server resources can now hold the stop: a resource asks once, as it starts, and releases each stop once its saves are done. Observer sendsquitonce the 5 seconds have passed and every resource that holds the stop has released it, and at the owner's limit at the latest, naming who did not release it. A resource that never asks changes nothing.This branch holds
mainup to #60: contract 7 follows #58's 5 and #60's 6, and its migration is0022.What changed
holdShutdownandreleaseShutdown, andobserver:shuttingDowna new field,maxDelayMs: whenquitcomes whoever still holds the stop.resource/CONTRACT.mdhas a new section, "Holding a stop": what the exports return (true, orfalsewithtooMany,invalid,notStoppingornotHeld), how long a hold lasts, and a table of what waits for the resources and what does not. The example resource holds each stop for as long as itsobserver_example_save_msconvar says, 0 by default, and releases it even when its save fails.GetInvokingResource(), 64 at most, and forgets a resource's hold when the resource stops; a bridge started again holds nothing. It reports the whole list: once afterready, grouped within 500 ms before a stop, and at once during one. Right after it raisesobserver:shuttingDown, it reports who still holds the stop, once every handler has run up to its first wait.quitfollows as before. A stop that overtakes another (a stop during a restart, Observer stopping during a scheduled restart) keeps the first one's wait and limit, and itsobserver:shuttingDowngives what is left of both. A crash, a hang, aquittyped in the console and a closing Windows console wait for no one, and the console says who was left when a resource held the stop. The names the bridge reports lose their control characters and are cut at 64 characters before anything shows them.0022_stop_hold.sqladds the column, with its default and its check. At 5 seconds, no resource is waited for longer than before.POST /api/servers/default/stop/now, behind the server control permission, has the stop under way go on without the resources that still hold it; the 5 seconds still run to their end. With no resource holding a stop, it answersstop_not_held.server_stop_hold_expired, by the system, a failure: the resources that held the stop past their limit, or when Windows closed the console, the reason and how long Observer waited.server_stop_hold_skipped: who had FXServer quit without waiting, and the resources left behind.EXTEND_TIMEOUT_USECevery 5 seconds, and for 40 seconds after the wait, so a unit already installed withTimeoutStopSec=45needs no change (systemd 236 and later). A stop no resource holds asks systemd for nothing, as before.fake_holders <names>,fake_release_msandfake_release_ms:<name>in the cfg, andfake_hold <resource>andfake_release <resource>on stdin: resources that hold each stop and release it after a time, or never.Why
A fixed delay cannot fit every server: a big one saves for longer than 5 seconds, and data cut off halfway is lost or corrupted. txAdmin has nothing more than the same fixed delay (
shutdownNoticeDelayMs). Any server resource can hold a stop, as any can give a label: the owner's limit bounds what one can cost, and Observer names every resource that holds one.Notes for review
main(3eb487d) came after Run the fake FXServer tests on ports the system gives, so that two runs on one host no longer collide #56 to Keep ban templates for the ban dialog, and edit an active ban under a permission of its own #62. The migration moved from0019_stop_hold.sqlto0022_stop_hold.sql, unchanged, after Keep ban templates for the ban dialog, and edit an active ban under a permission of its own #62's0019and Let in approved players only, and close the server for maintenance to all but a list, under another name in the server list, in contract 6 #60's0020and0021; its test now starts from a0021database. Contract 7 follows the 5 of Kick a player or everyone, and raise announcements and direct messages for the server's resources, in contract 5 #58 and the 6 of Let in approved players only, and close the server for maintenance to all but a list, under another name in the server list, in contract 6 #60, and "Version 7" heads the history inresource/CONTRACT.md. No permission bit is taken: Quit now goes through server control.ready;Report::HoldsandEventKind::StopHeldcome after Follow the server's resources on a Resources page, and start, stop, restart or rescan them #57'sResourceList;ErrorCode::StopNotHeldanswers 409 beside List the server's entities, draw them on the live map and delete them a hundred at a time #61's entities codes;AuditAction::ALLcounts 136. In the fake FXServer, Follow the server's resources on a Resources page, and start, stop, restart or rescan them #57 movedstop,restartandstartinto methods of their own: a resource that stops goes through them, and a stopped bridge forgets its holds there. InServerControls.tsx, Quit now's line sits under Kick a player or everyone, and raise announcements and direct messages for the server's resources, in contract 5 #58's menu and Let in approved players only, and close the server for maintenance to all but a list, under another name in the server list, in contract 6 #60's Maintenance; the test keeps Kick a player or everyone, and raise announcements and direct messages for the server's resources, in contract 5 #58'scontrols(state, launch, permissions)and takes the status it opens on last.resource/CONTRACT.mdwith no change on this side:holdShutdownandreleaseShutdownare shown called under "Holding a stop", andmaxDelayMsis in the table ofobserver:shuttingDown, which the page turns into its card and its handler. No section of the contract was renamed.launchin the supervisor was 101 lines, past clippy's 100: the address a spawn is probed at moved into a method of its own, in a commit of its own (e767c21), as List the server's entities, draw them on the live map and delete them a hundred at a time #61 did for its topics.LaunchSettingsgainsstop_hold_seconds,ServerStatusgainsstop_holdersandstop, andStopTimingsgainshold: Follow the server's resources on a Resources page, and start, stop, restart or rescan them #57's panel fixture inResourcesPage.test.tsxgets the two status fields, in the merge.TriggerEventtells Observer who still holds the stop once the handlers have run, and a resource that asks while the stop is under way is waited for too.Verified
e767c21, this branch withmainmerged in:cargo xtask checkin one run, under Node 26 (the CI's Node 24 is not on this host): fmt, icons, clippy-D warningswith and withoutobserver/test-catalog, the workspace's 2,986 tests, bindings unchanged, then inweb/format, lint, type check, 960 tests and the build. The merge commit alone passedcargo test --workspace --no-fail-fast(2,986 tests, none failed), the panel's format, lint, type check, 960 tests and build, andcargo xtask bindings, whose output differs frommainonly by this branch's two new types and five changed ones.e767c21, run ascargo xtask e2eruns them but on the Chromium already on this host (the build withobserver/test-catalog, Playwright, ports of their own): 12 passed, among them FXServer restarted, stopped and started from the top bar and Kick a player or everyone, and raise announcements and direct messages for the server's resources, in contract 5 #58's kick from the players column; the 2 that run on Linux only were skipped.crates/fake-fxserver/tests/supervisor.rs, one test per case: a stop held then released; a stop no resource holds, as before; a resource that never releases, named at the limit; the schedule's restarts and a single restart someone planned; Observer stopping, by itself and in the place of a scheduled restart; a new launch applied by a restart; a crash and a hang, which wait for no one; a closing console, before and during the wait; a stuck main loop, waited for until the limit, then killed; a resource that stops or restarts; the bridge started again; a stop that overtakes a restart; Quit now and its refusal; a resource that asks during the notice; a typedquit; the owner's limit and its change.runtime.rs: the audit of a stop past its limit and of a skipped wait, and the status the panel reads.pre_launch.rs: the database backup before a restart begins once the resources released the stop and FXServer exited.0021; the launch audit; when Observer asks systemd for time; and in the panel the status line, Quit now and its refusal, the setting's save and range, the live status validator and the audit details. Show the integration contract in the panel, on a Documentation page drawn from the file the binary ships #59's contract tests pass on contract 7: every event has its section and its row, every export is shown called, the example listens to every event.0022applied in order, two resources that hold each stop, one released after 6 seconds): the status card with its wait, its countdown and Quit now, at 1440 and 375 px in both themes, its controls and the line of the wait measured within the card at 375 px (317 px of 317); the same card online, with Kick a player or everyone, and raise announcements and direct messages for the server's resources, in contract 5 #58's menu and Let in approved players only, and close the server for maintenance to all but a list, under another name in the server list, in contract 6 #60's Maintenance; Quit now's confirmation, then the stop, andserver_stop_hold_skippedin the audit log with the resource left behind; Settings → Launch with the limit and the resources that hold each stop; the Documentation page, which shows contract 7, its section "Holding a stop" andmaxDelayMsin the card ofobserver:shuttingDown. No console error and no sideways scroll on any of them.Not verified
TriggerEventruns every handler before it returns (EventScriptFunctions.cpp,ResourceEventComponent.cpp), that an export returns every value to a Lua caller (scheduler.lua) and thatGetInvokingResource()names the calling resource (ResourceScriptFunctions.cpp); none of it was exercised here, noronResourceStopdropping a hold during a stop.crates/observer/src/systemd.rswas not compiled, as no WSL distribution or cross C compiler was available, and no realsystemctl stopwas held past 45 seconds. When Observer asks systemd for time is tested on Windows; the lines that send the ask are not.maindid not touch the file, so the merge left it as it was.observer fxserver runhas no option for the limit: it waits 60 seconds.