fix(rust): an update on a running server reloads the plugin
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 52s
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 52s
The plugin was placed with write_atomic: remove the old file, rename a .tmp over it. Oxide and Carbon watch the plugins directory and reload on a change; both saw the remove as a delete and UNLOADED the bridge, and ignored the rename, so the new file was never loaded. Every `update` (or re-install) on a running server took the bridge down until the next boot, silently — the phase 18 walk found it on all three instances and both frameworks, after the move to v0.1.1. The plugin is now overwritten in place, which is what an operator's `cp` does and what both frameworks reload on. write_atomic stays for the record and the configs, where atomicity is the point. `update` on the same bundle also no longer says "nothing moved" after it has just put back a hand-edited plugin — the remedy `doctor` names. Walked: hand-edit, then `update`, on a running Oxide and a running Carbon server: replaced, reloaded, reconnected, doctor clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -324,7 +324,17 @@ pub fn deploy(cli: &Cli, mode: Mode) -> Result<()> {
|
||||
let before = prior.as_ref().map(|p| p.bundle.clone());
|
||||
match before {
|
||||
Some(b) if b.tag == bundle.bundle => {
|
||||
println!("\nAlready on bundle {} — nothing moved.", bundle.bundle)
|
||||
// The same bundle can still have written something: a plugin edited by hand is
|
||||
// put back, which is what `doctor` tells an operator to run `update` for.
|
||||
let restored = planned.iter().filter(|p| p.plugin_action.writes()).count();
|
||||
if restored == 0 && !binary_action.writes() {
|
||||
println!("\nAlready on bundle {} — nothing moved.", bundle.bundle)
|
||||
} else {
|
||||
println!(
|
||||
"\nAlready on bundle {} — put back what no longer matched it (see above).",
|
||||
bundle.bundle
|
||||
)
|
||||
}
|
||||
}
|
||||
Some(b) => {
|
||||
println!(
|
||||
@@ -656,7 +666,16 @@ fn deploy_instance(
|
||||
if plan.plugin_action.writes() {
|
||||
std::fs::create_dir_all(plan.server.plugins_dir())
|
||||
.with_context(|| format!("cannot create {}", plan.server.plugins_dir().display()))?;
|
||||
write_atomic(&plugin_path, &released.source)?;
|
||||
// Overwritten in place, NOT `write_atomic`. Oxide and Carbon watch the plugins directory
|
||||
// and reload on a CHANGE; `write_atomic` removes the old file and renames a `.tmp` over it,
|
||||
// which both frameworks see as a delete — they unload the bridge — and a rename they
|
||||
// ignore, so the new file is never loaded. On a running server that was an `update` that
|
||||
// silently took the bridge down until the next boot (the phase 18 walk, on all three
|
||||
// instances and both frameworks). A write in place is what an operator's `cp` does, and
|
||||
// it reloads. A crash mid-write leaves a file that fails to compile; `doctor` reports it
|
||||
// as not the deployed file and `update` writes it again.
|
||||
std::fs::write(&plugin_path, &released.source)
|
||||
.with_context(|| format!("cannot write {}", plugin_path.display()))?;
|
||||
ui::ok(&format!(
|
||||
"plugin {} {}",
|
||||
if plan.plugin_action == BinaryAction::Replace {
|
||||
|
||||
Reference in New Issue
Block a user