mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
MCP: a control refusal without an id reports the app's reason instead of an id mismatch (#1728)
This commit is contained in:
1 parent
cc518797a8
commit
d3327b17a1
2 files changed
+36
No files matched your search
@@ -94,6 +94,13 @@ impl Backend for Remote {
|
||||
format!("VectorCraft app at {}: `{method}` failed: {e}; operation may have completed; inspect state before retrying", self.addr)
|
||||
})?;
|
||||
let v: Value = serde_json::from_str(reply.trim()).map_err(|e| format!("bad reply from app: {e}"))?;
|
||||
if v.get("id").is_none() && v.get("ok").and_then(Value::as_bool) == Some(false) {
|
||||
// The app's refusals carry no id (too many connections, a line it would not read):
|
||||
// it ran nothing and closes the connection, so the next call starts a fresh one.
|
||||
self.conn = None;
|
||||
let why = v.get("error").and_then(Value::as_str).unwrap_or("unknown error");
|
||||
return Err(format!("VectorCraft app at {} refused `{method}`: {why}; nothing was run", self.addr));
|
||||
}
|
||||
if v.get("id") != Some(&json!(id)) {
|
||||
// Not the answer to this call: the stream is desynchronised (or the endpoint faulty),
|
||||
// so the connection is dropped and the next call starts a fresh one. The operation may
|
||||
|
||||
@@ -542,6 +542,35 @@ fn remote_rejects_a_reply_with_a_mismatched_id_and_reconnects() {
|
||||
assert_ne!(seen[0]["id"], seen[1]["id"]);
|
||||
}
|
||||
|
||||
/// The app's refusals carry no id (a 17th connection gets one and is closed). The call reports the
|
||||
/// app's reason, not a mismatch, says nothing ran, and the next call starts a fresh connection.
|
||||
#[test]
|
||||
fn remote_reports_the_reason_of_a_refusal_without_an_id() {
|
||||
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let addr = listener.local_addr().unwrap().to_string();
|
||||
let h = std::thread::spawn(move || {
|
||||
let (mut refused, _) = listener.accept().unwrap();
|
||||
// The request is read first, so the refusal answers it and the close is orderly.
|
||||
refused.set_read_timeout(Some(std::time::Duration::from_secs(5))).unwrap();
|
||||
let _ = BufReader::new(refused.try_clone().unwrap()).read_line(&mut String::new());
|
||||
writeln!(refused, "{}", json!({"ok": false, "error": "too many control connections"})).unwrap();
|
||||
drop(refused);
|
||||
let (stream, _) = listener.accept().unwrap();
|
||||
stream.set_read_timeout(Some(std::time::Duration::from_secs(5))).unwrap();
|
||||
let mut out = stream.try_clone().unwrap();
|
||||
let mut line = String::new();
|
||||
BufReader::new(stream).read_line(&mut line).unwrap();
|
||||
let msg: Value = serde_json::from_str(&line).unwrap();
|
||||
writeln!(out, "{}", json!({"ok": true, "result": 7, "id": msg["id"]})).unwrap();
|
||||
});
|
||||
let mut remote = Remote::connect(&addr).unwrap();
|
||||
let err = remote.call("document.inspect", json!({})).expect_err("a refusal is an error");
|
||||
assert!(err.contains("too many control connections") && err.contains("nothing was run"), "{err}");
|
||||
assert!(!err.contains("mismatch") && !err.contains("may have completed"), "{err}");
|
||||
assert_eq!(remote.call("document.inspect", json!({})), Ok(json!(7)), "the next call reconnects");
|
||||
h.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_connect_fails_fast() {
|
||||
// A port this test holds, bound but not listening, so connecting to it is refused. (A port
|
||||
|
||||
Reference in new issue
Block a user