Notify replaced plugin processes at upgrade grace start
Send rpc.superseded with the newly active hash before activation returns. Add an opt-in SDK hook and drain helper that closes SDK listeners and idle connections without cancelling active handlers or background work. Document the additive notice and preserve existing grace, stop and restart rules. Cover immediate notices, ignored notices, early service restarts, SDK draining and duplicate-hook diagnostics. Card: SAU-352
This commit is contained in:
@@ -201,6 +201,88 @@ fn protocol() {
|
||||
assert_eq!(outgoing["method"], "other.background");
|
||||
p.answer(&outgoing, json!({}));
|
||||
assert!(p.child.wait().unwrap().success());
|
||||
// Replacement is a notification, not EOF. Drain keeps both endpoint handlers
|
||||
// and background calls alive, while closing idle connections and the listener.
|
||||
assert!(
|
||||
!d["result"]["methods"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|m| m["name"] == "rpc.superseded" || m["name"] == "superseded")
|
||||
);
|
||||
std::fs::remove_file(dir.join("stopped")).unwrap();
|
||||
let mut provider = start(&wasm, &dir);
|
||||
provider.request("serve", json!({}));
|
||||
assert_eq!(provider.read()["result"], Value::Null);
|
||||
provider.request("start", json!({}));
|
||||
let first = provider.read();
|
||||
let second = provider.read();
|
||||
let background = if first["method"] == "other.background" {
|
||||
first
|
||||
} else {
|
||||
second
|
||||
};
|
||||
assert_eq!(background["method"], "other.background");
|
||||
let address = std::fs::read_to_string(dir.join("endpoint"))
|
||||
.unwrap()
|
||||
.split_whitespace()
|
||||
.next()
|
||||
.unwrap()
|
||||
.trim_start_matches("tcp:")
|
||||
.to_owned();
|
||||
let mut idle = std::net::TcpStream::connect(&address).unwrap();
|
||||
idle.set_read_timeout(Some(std::time::Duration::from_secs(4)))
|
||||
.unwrap();
|
||||
writeln!(
|
||||
idle,
|
||||
"{}",
|
||||
json!({"jsonrpc":"2.0","id":0,"method":"rpc.endpoint","params":{}})
|
||||
)
|
||||
.unwrap();
|
||||
let mut idle = BufReader::new(idle);
|
||||
let mut line = String::new();
|
||||
idle.read_line(&mut line).unwrap();
|
||||
assert_eq!(
|
||||
sausage_plugin::serde_json::from_str::<Value>(&line).unwrap()["id"],
|
||||
0
|
||||
);
|
||||
let mut active = std::net::TcpStream::connect(&address).unwrap();
|
||||
active
|
||||
.set_read_timeout(Some(std::time::Duration::from_secs(4)))
|
||||
.unwrap();
|
||||
writeln!(
|
||||
active,
|
||||
"{}",
|
||||
json!({"jsonrpc":"2.0","id":1,"method":"wait","params":{}})
|
||||
)
|
||||
.unwrap();
|
||||
let inflight = provider.read();
|
||||
assert_eq!(inflight["method"], "other.inflight");
|
||||
provider
|
||||
.send(json!({"jsonrpc":"2.0","method":"rpc.superseded","params":{"current":"new-hash"}}));
|
||||
let hook = provider.read();
|
||||
assert_eq!(hook["method"], "other.superseded");
|
||||
assert_eq!(hook["params"]["current"], "new-hash");
|
||||
provider.answer(&hook, json!({}));
|
||||
assert_eq!(provider.read()["params"]["name"], "draining");
|
||||
line.clear();
|
||||
assert_eq!(idle.read_line(&mut line).unwrap(), 0);
|
||||
assert!(std::net::TcpStream::connect(&address).is_err());
|
||||
assert!(provider.child.try_wait().unwrap().is_none());
|
||||
provider.answer(&inflight, json!({}));
|
||||
line.clear();
|
||||
BufReader::new(active).read_line(&mut line).unwrap();
|
||||
assert_eq!(
|
||||
sausage_plugin::serde_json::from_str::<Value>(&line).unwrap()["result"],
|
||||
"finished"
|
||||
);
|
||||
assert!(provider.child.try_wait().unwrap().is_none());
|
||||
provider.answer(&background, json!({}));
|
||||
provider.finishes();
|
||||
assert!(
|
||||
!dir.join("stopped").exists(),
|
||||
"drain is not the EOF stop hook"
|
||||
);
|
||||
let provider_doc = &d["result"];
|
||||
let imports = target.join("imports");
|
||||
std::fs::create_dir_all(&imports).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user