feat(server): write back and flush the world on a graceful stop #8
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
scope/assets
scope/client
scope/networking
scope/renderer
scope/scripting
scope/server
scope/shared
scope/workspace
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Synvael/synvael!8
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/graceful-stop"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What does this PR do?
I gave the server a stop path that saves the world before exiting.
ServerWorld::shutdown(self)incrates/server/src/world_server.rstears the world down in channel order. It evicts every resident chunk throughreconcile(&HashSet::new())so each one queues itsWriteorRemove, dropsjob_tx, joins every generation worker, sends a finalFlushand waits for the reply, then drops its save sender and joins the save actor. Workers are joined while the actor is still alive because a worker mid-load is blocked on aReadreply.SaveActor::shutdown(self)incrates/server/src/save/region_actor.rsdrops the actor's own sender and joins its thread. A panicked worker or actor thread is logged withwarn!and the teardown continues.request_flushhelper soflushandshutdownshare it.maininstalls actrlchandler (with theterminationfeature, so SIGTERM as well as SIGINT) before the loading phase. The handler only swaps anArc<AtomicBool>; a second signal while the flag is already set exits with status 130.run_simulationnow checks the flag at the top of each tick and returns instead of looping forever. A newshut_downfunction inmain.rsthen drops theNetworkServer, removesServerWorldfrom the ECS world, callsshutdown, and logs the elapsed time. A failed final flush makes the process exit with an error.#[expect(dead_code)]placeholders onSaveActor.handle,ServerWorld.save_actorandServerWorld.workers, along with their TODO.docs/save_format.mdnow states that a graceful stop writes back and flushes, and that a kill without a signal still loses up to one autosave interval.Why is this change necessary?
The server had no way to stop.
run_simulationreturned!and a signal killed the process wherever it landed. A chunk evicted since the last autosave had its diff only in the save actor's in-memory region image, and resident chunks that were never evicted had never been diffed at all, so both were lost on every exit. The 45 second autosave narrowed that window without closing it.Scope of Changes
Testing
Automated:
cargo check --workspace --all-targets: passes.cargo clippy --all-targets --all-features -- -D warnings: passes.cargo fmt --all -- --check: passes.cargo test -p server: 56 passed, 0 failed.cargo deny check: advisories, bans, licenses and sources all ok withctrlcadded.Two new tests in
crates/server/src/tests/world_server.rs:shutdown_writes_back_resident_editsedits a resident chunk, callsshutdownwith no manual reconcile or flush, and checks that a fresh world over the same directory streams the edit back.shutdown_with_loads_in_flight_returnsdispatches a radius 4 cylinder and shuts down without draining, which proves the join order cannot deadlock while workers are mid-load.Manual:
Additional Context
This adds
ctrlc3.5.2 to the server, which brings innixon Unix and two macOS-only crates (dispatch2,block20.6). The workspace already hadblock20.5.1 through the client, so the lockfile now carries two versions of it;cargo denyaccepts that.Connected clients still see a server stop as an idle timeout.
NetworkServer's drop path does not sendDisconnectto open sessions yet; that is a separate change innet.Related Issues
None.
Checklist
dev(or a feature branch offdev) and my PR targetsdev.///doc comments for Rust) where necessary.