Skip to main content

mj_controller/controller/
worker_binary.rs

1//! Worker binary acquisition, profile staging, and worker installation.
2
3use std::collections::{HashMap, HashSet};
4use std::fs::File;
5use std::io::{ErrorKind, Read, Write};
6use std::path::{Path, PathBuf};
7use std::sync::OnceLock;
8use std::time::Instant;
9
10use anyhow::{Context, Result, bail, ensure};
11use rayon::prelude::*;
12use sha2::{Digest, Sha256};
13
14use crate::session_manager::{
15    ProjectMemorySyncTarget, RemoteWorkerBinaryRefresh, WorkerBinaryRefresh,
16    WorkerBinaryRefreshPlan, WorkerLaunchRefreshPlan, WorkerRecoveryPlan, WorkerWorkspace,
17};
18use crate::targets::{self, CommandExecutor, CommandPlan, CommandSpec, ProvisionStage, SshTarget};
19use mj_core::config::{
20    HarnessKind, HarnessProfile, ProjectBundle, ProjectRepository, atomic_write, data_dir,
21};
22use mj_core::harness_runtime::{CLAUDE_ACP_VERSION, CODEX_ACP_PACKAGE, CODEX_ACP_VERSION};
23use mj_core::project_memory::{ProjectMemoryIdentity, RepositoryMemoryIdentity};
24use mj_core::worker_launch::{
25    HarnessRuntimePolicy, ProjectMemoryLaunchConfig, ProjectMemoryMcpDelivery, WorkerLaunchConfig,
26    WorkerOwnership,
27};
28
29use super::backend::backend_locator;
30use super::readiness::{
31    WORKER_EXIT_RECORD_MARKER, WORKER_PROCESS_MARKER, WORKER_STARTUP_RECORD_MARKER,
32};
33use super::{Controller, execute_checked, target_profile_home};
34
35/// Run a `reqwest::blocking` request on a dedicated OS thread and return its
36/// result.
37///
38/// A `reqwest::blocking::Client` owns a private Tokio runtime and drops it when
39/// the client is dropped. Dropping a runtime while the current thread has a
40/// Tokio `block_on` context entered panics with "Cannot drop a runtime in a
41/// context where blocking is not allowed". The session-move lifecycle drives
42/// this otherwise synchronous staging code under `Handle::block_on` (see
43/// `daemon::session_move`), so the parent thread does have such a context
44/// entered. A freshly spawned OS thread has entered no runtime, so the client's
45/// runtime is created and dropped there without tripping that check. Every
46/// caller of these HTTP helpers is protected, not just the move path.
47fn on_dedicated_thread<T: Send>(work: impl FnOnce() -> Result<T> + Send) -> Result<T> {
48    std::thread::scope(|scope| {
49        scope.spawn(work).join().unwrap_or_else(|panic| {
50            Err(anyhow::anyhow!(
51                "blocking HTTP thread panicked: {}",
52                targets::command_thread_panic_message(panic.as_ref())
53            ))
54        })
55    })
56}
57
58mod launch;
59mod staging;
60pub(super) use staging::*;
61mod project_memory;
62use project_memory::*;
63mod binary_source;
64pub use binary_source::*;
65mod binary_select;
66pub(super) use binary_select::*;
67mod harness;
68pub(super) use harness::*;
69mod catalog;
70pub(super) use catalog::*;
71mod install;
72pub(super) use install::*;
73mod upgrade;
74pub(crate) use upgrade::*;
75mod process;
76pub(super) use process::*;
77
78#[cfg(test)]
79mod tests;