Skip to content

Commit d00a49a

Browse files
committed
Update power handling to review-protocol 0.19.0
Align roxyd's node power handling with review-protocol 0.19.0 semantics: immediate Reboot/Shutdown are fire-and-forget on the wire and are spawned as background tasks (Linux only). Graceful operations still follow request/response and return NodePowerResponse::Initiated. Remove the PendingPowerOperation/PowerAction types and the old release-ordering machinery; simplify dispatch to call review_protocol::request::handle directly. Retain PowerExecutor and MockPowerExecutor for testability. Update CHANGELOG; roxyd tests pass and clippy is clean.
1 parent 2fc0245 commit d00a49a

3 files changed

Lines changed: 64 additions & 228 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,14 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
88

99
### Added
1010

11-
- Implement the full `node_power` family directly in `roxyd`. Immediate
12-
`NodePowerRequest::Reboot` and `NodePowerRequest::Shutdown` (and the legacy
13-
flat `reboot`/`shutdown` compatibility requests) now prepare a pending
14-
reboot or power-off operation and only execute the destructive system call
15-
after `roxyd` has written `NodePowerResponse::Initiated` successfully on
16-
the request stream. If the response write fails, the pending operation is
17-
cancelled without rebooting or powering off. Immediate variants remain
18-
Linux-only and return `"invalid command"` on other platforms.
19-
`NodePowerRequest::GracefulReboot` and `NodePowerRequest::GracefulShutdown`
20-
spawn the platform's standard reboot/poweroff command and return
21-
`"fail"` if the process could not be started.
11+
- `roxyd` now handles node power-control requests from a Manager (immediate
12+
and graceful reboot/shutdown), replacing the previous unimplemented
13+
scaffolding. On Linux, immediate reboot and shutdown run in the background;
14+
grouped `node_power` requests do not return a protocol response for these
15+
operations. Graceful reboot and shutdown spawn the platform's standard
16+
reboot or poweroff command and report success or `"fail"` to the Manager.
17+
Legacy flat `reboot` and `shutdown` requests use the same behavior.
18+
Immediate reboot and shutdown are not supported on non-Linux platforms.
2219

2320
## [0.6.0] - 2026-04-16
2421

src/bin/roxyd/control.rs

Lines changed: 3 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -227,18 +227,7 @@ async fn dispatch(send: &mut quinn::SendStream, recv: &mut quinn::RecvStream) ->
227227
dispatch_with_executor(send, recv, Arc::new(SystemPowerExecutor)).await
228228
}
229229

230-
/// Power-aware dispatch entry that takes a caller-supplied
231-
/// [`PowerExecutor`].
232-
///
233-
/// Immediate `NodePowerRequest::Reboot` and `NodePowerRequest::Shutdown`
234-
/// (and the legacy flat `reboot`/`shutdown` compatibility paths) prepare a
235-
/// [`handlers::power::PendingPowerOperation`] inside the handler but do not
236-
/// execute the destructive system call. After
237-
/// [`review_protocol::request::handle`] returns successfully — which means
238-
/// the `NodePowerResponse::Initiated` response has been written to the
239-
/// stream — this function releases the pending operation. If the response
240-
/// write fails, the pending operation is dropped without being released and
241-
/// the executor is never invoked.
230+
/// Power-aware dispatch entry that takes a caller-supplied [`PowerExecutor`].
242231
///
243232
/// # Errors
244233
///
@@ -249,19 +238,9 @@ async fn dispatch_with_executor(
249238
executor: Arc<dyn PowerExecutor>,
250239
) -> Result<()> {
251240
let mut handler = RequestHandler::new(executor);
252-
let result = review_protocol::request::handle(&mut handler, send, recv)
241+
review_protocol::request::handle(&mut handler, send, recv)
253242
.await
254-
.map_err(|e| anyhow::anyhow!("{e}"));
255-
256-
if result.is_ok() {
257-
// The response stream wrote successfully; release any prepared
258-
// immediate power operations so they proceed to reboot/poweroff.
259-
let _join_handles = handler.power.release_pending();
260-
}
261-
// On error, `handler` is dropped here; any unreleased pending power
262-
// operations observe the closed sender and exit without rebooting.
263-
264-
result
243+
.map_err(|e| anyhow::anyhow!("{e}"))
265244
}
266245

267246
/// Request handler that maps review-protocol requests into roxyd handlers.
@@ -1191,35 +1170,6 @@ mod tests {
11911170
/// reaches the peer (simulated here by closing the stream from the
11921171
/// server side after the connection-level send), the pending immediate
11931172
/// power operation is not released and the executor is never invoked.
1194-
#[tokio::test]
1195-
async fn pending_reboot_cancelled_on_dispatch_error() {
1196-
use std::sync::atomic::Ordering;
1197-
1198-
// Build a handler and call into it directly to simulate the dispatch
1199-
// path. This isolates the cancellation invariant from the QUIC
1200-
// transport.
1201-
let mock = Arc::new(handlers::power::MockPowerExecutor::default());
1202-
let mut handler = super::RequestHandler::new(mock.clone() as Arc<dyn PowerExecutor>);
1203-
1204-
// Drive the node_power handler directly. On non-Linux this returns
1205-
// an error and there is nothing pending — the assertion holds.
1206-
let _ = review_protocol::request::Handler::node_power(
1207-
&mut handler,
1208-
review_protocol::types::node::NodePowerRequest::Reboot,
1209-
)
1210-
.await;
1211-
1212-
// Drop without calling release_pending — simulates a response-write
1213-
// failure in dispatch_with_executor.
1214-
drop(handler);
1215-
1216-
for _ in 0..20 {
1217-
tokio::task::yield_now().await;
1218-
}
1219-
1220-
assert_eq!(mock.reboot_count.load(Ordering::SeqCst), 0);
1221-
}
1222-
12231173
#[tokio::test]
12241174
async fn dispatch_resource_usage_over_live_connection() {
12251175
let (inner, server, _endpoint) = setup_test_connection().await;

0 commit comments

Comments
 (0)