agent_engine/daemon/extension_reload.rs
1//! `ClientFrame::ReloadExtension` (`synaps daemon reload-extension <NAME>`):
2//! stop one extension and start it again from what is installed now,
3//! without `daemon reload` (which checkpoints every session and cancels the
4//! turns in flight).
5//!
6//! Extensions are daemon-wide: one process each, shared by every session
7//! through the host's tool registry, hook bus and lease manager. The reload
8//! updates those in place ([`ExtensionManager::reload_from_disk`]), so every
9//! session gets the new version from its next request; nothing in a session
10//! is touched. A call into the old process that is in flight when it stops
11//! fails like any extension crash would.
12//!
13//! [`ExtensionManager::reload_from_disk`]: crate::extensions::manager::ExtensionManager::reload_from_disk
14
15use std::sync::atomic::Ordering;
16use std::sync::Arc;
17
18use super::DaemonState;
19use crate::session::wire::{DaemonFrame, RefuseReason};
20
21/// Serve `ReloadExtension{extension}`. Always answers with one frame:
22/// `ExtensionReloaded`, `ExtensionReloadFailed` or `Refused{Busy}`.
23pub async fn reload_extension(state: &Arc<DaemonState>, extension: &str) -> DaemonFrame {
24 // A daemon reload shuts every extension down and starts them again
25 // itself; a single reload racing it could start a process the exec
26 // then orphans.
27 if state.reloading.load(Ordering::SeqCst) {
28 return DaemonFrame::Refused { reason: RefuseReason::Busy, message: "daemon reloading; retry in a moment".into() };
29 }
30 // Write lock for the whole stop-and-start: two reloads of one extension
31 // cannot interleave, and discovery (which holds it for its walk) has
32 // finished before this runs.
33 let mut manager = state.host.ext_manager().write().await;
34 let was_loaded = manager.is_loaded(extension);
35 match manager.reload_from_disk(extension).await {
36 Ok(report) => DaemonFrame::ExtensionReloaded { report },
37 Err(failure) => DaemonFrame::ExtensionReloadFailed {
38 extension: extension.to_string(),
39 message: failure.concise_message(),
40 unloaded: was_loaded && !manager.is_loaded(extension),
41 },
42 }
43}