From 4abe14cbbfb343afe0fb335075daf0a675e4124e Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 3 Aug 2026 19:50:17 -0700 Subject: [PATCH 1/2] chore(rpc): adopt the prpc empty-body encoding for unit responses prpc-build 0.7.0 encodes a `google.protobuf.Empty` response as an empty JSON body instead of the literal `null`, matching what the protobuf codec has always done. Decode responses through `prpc::codec::decode_json_from_slice` so an empty body maps back to the unit type. Upstream: https://github.com/Phala-Network/prpc/pull/1 --- dstack/Cargo.lock | 8 ++++---- dstack/Cargo.toml | 4 ++-- dstack/ra-rpc/src/client.rs | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/dstack/Cargo.lock b/dstack/Cargo.lock index 6ae5b810a..367039477 100644 --- a/dstack/Cargo.lock +++ b/dstack/Cargo.lock @@ -5536,9 +5536,9 @@ dependencies = [ [[package]] name = "prpc" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd65145222d0e76bf84c71bb0e9abcf45779d032fb0933fe1cceeb11be1f06a9" +checksum = "56168f524507cff007f4399ca18e645a18960c9c5e84fca98a24d11c56c85687" dependencies = [ "anyhow", "async-trait", @@ -5555,9 +5555,9 @@ dependencies = [ [[package]] name = "prpc-build" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0db191928d08a5e73122ee34e0003b23c5427e40aa0057394d1009f67c3aa5eb" +checksum = "af89d66c95988e7da1ce83bd9353e1ce7eb22ce6ba6faf5ddc6cc97aa87b3e20" dependencies = [ "either", "fs-err", diff --git a/dstack/Cargo.toml b/dstack/Cargo.toml index 18c4c171d..420d6a227 100644 --- a/dstack/Cargo.toml +++ b/dstack/Cargo.toml @@ -261,8 +261,8 @@ x509-parser = "0.16.0" pkcs8 = { version = "0.10", default-features = false } # RPC/Protocol -prpc = "0.6.0" -prpc-build = "0.6.1" +prpc = "0.6.2" +prpc-build = "0.7.0" # Development/Testing bindgen = "0.71.1" diff --git a/dstack/ra-rpc/src/client.rs b/dstack/ra-rpc/src/client.rs index 62e52f442..28c4ed9a0 100644 --- a/dstack/ra-rpc/src/client.rs +++ b/dstack/ra-rpc/src/client.rs @@ -211,7 +211,8 @@ impl RequestClient for RaClient { .await .context("Failed to read response")? .to_vec(); - let response = serde_json::from_slice(&body).context("Failed to deserialize response")?; + let response = + prpc::codec::decode_json_from_slice(&body).context("Failed to deserialize response")?; Ok(response) } } From 9887987bc1a9ea5f9ceadd6722e58619acf6fece Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 3 Aug 2026 20:17:35 -0700 Subject: [PATCH 2/2] fix(http-client): decode unit responses in PrpcClient too `PrpcClient` is the second `RequestClient` impl in the tree and still used `serde_json::from_slice`, so an empty unit body would fail to parse at runtime. `dstack-cli-core`'s `stop_vm` / `remove_vm` go through it and would have reported a failure for a request that actually succeeded. Compile checks cannot catch this: `serde_json::from_slice::<()>` type-checks fine and only fails on the empty input at runtime. --- dstack/http-client/src/prpc.rs | 5 ++++- dstack/ra-rpc/src/client.rs | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/dstack/http-client/src/prpc.rs b/dstack/http-client/src/prpc.rs index e26e5bb64..7818463a8 100644 --- a/dstack/http-client/src/prpc.rs +++ b/dstack/http-client/src/prpc.rs @@ -63,7 +63,10 @@ impl RequestClient for PrpcClient { if status != 200 { anyhow::bail!("Invalid status code: {status}, path={path}"); } - let response = serde_json::from_slice(&body).context("Failed to deserialize response")?; + // Not serde_json::from_slice: a unit response arrives as an empty body, which + // is not valid JSON. Every RequestClient impl has to decode through this. + let response = + prpc::codec::decode_json_from_slice(&body).context("Failed to deserialize response")?; Ok(response) } } diff --git a/dstack/ra-rpc/src/client.rs b/dstack/ra-rpc/src/client.rs index 28c4ed9a0..5211dbcfa 100644 --- a/dstack/ra-rpc/src/client.rs +++ b/dstack/ra-rpc/src/client.rs @@ -211,6 +211,8 @@ impl RequestClient for RaClient { .await .context("Failed to read response")? .to_vec(); + // Not serde_json::from_slice: a unit response arrives as an empty body, which + // is not valid JSON. Every RequestClient impl has to decode through this. let response = prpc::codec::decode_json_from_slice(&body).context("Failed to deserialize response")?; Ok(response)