mj_controller/session_manager/
remote.rs1use super::*;
2
3pub(super) fn reconcile_action(
4 actor: Option<&RelaySessionTarget>,
5 desired: Option<&RelaySessionTarget>,
6) -> ReconcileAction {
7 match (actor, desired) {
8 (None, None) => ReconcileAction::Idle,
9 (None, Some(_)) => ReconcileAction::Spawn,
10 (Some(actor), Some(desired)) if actor == desired => ReconcileAction::Keep,
11 (Some(_), Some(_) | None) => ReconcileAction::Retire,
12 }
13}
14
15pub(super) fn target_map(targets: &[RelaySessionTarget]) -> BTreeMap<String, RelaySessionTarget> {
16 targets
17 .iter()
18 .cloned()
19 .map(|target| (target.session_id.clone(), target))
20 .collect()
21}
22
23pub(super) fn remove_actor_task(
24 actors: &mut BTreeMap<String, ActorRegistration>,
25 task_id: tokio::task::Id,
26) -> Option<String> {
27 let session_id = actors.iter().find_map(|(session_id, actor)| {
28 (actor.abort.id() == task_id).then(|| session_id.clone())
29 })?;
30 actors.remove(&session_id);
31 Some(session_id)
32}
33
34pub(super) fn reconcile_actors(
35 targets: &BTreeMap<String, RelaySessionTarget>,
36 actors: &mut BTreeMap<String, ActorRegistration>,
37 tasks: &mut tokio::task::JoinSet<String>,
38 updates: &CoalescedUpdateSender,
39) {
40 actors.retain(|session_id, actor| {
46 let live = !actor.commands.is_closed();
47 if !live {
48 tracing::warn!(session_id, "replacing stopped session relay actor");
49 }
50 live
51 });
52
53 for (session_id, actor) in actors.iter() {
54 let retiring = matches!(
55 reconcile_action(Some(&actor.target), targets.get(session_id)),
56 ReconcileAction::Retire
57 );
58 actor.retirement.send_replace(retiring);
59 }
60
61 for (session_id, target) in targets {
62 if !matches!(
63 reconcile_action(
64 actors.get(session_id).map(|actor| &actor.target),
65 Some(target)
66 ),
67 ReconcileAction::Spawn
68 ) {
69 continue;
70 }
71 let (actor_tx, actor_rx) = mpsc::channel(32);
72 let (release_tx, release_rx) = mpsc::unbounded_channel();
73 let (retirement_tx, retirement_rx) = watch::channel(false);
74 let (view_tx, view_rx) = watch::channel(ManagedSessionView::default());
75 let mut actor_updates = updates.for_actor(session_id);
76 actor_updates.observer = updates
77 .delegation
78 .as_ref()
79 .map(|feed| feed.register(session_id));
80 let task_target = target.clone();
81 let task_id = session_id.clone();
82 let abort = tasks.spawn(async move {
83 run_session_actor(
84 task_target,
85 actor_rx,
86 release_rx,
87 retirement_rx,
88 view_tx,
89 actor_updates,
90 )
91 .await;
92 task_id
93 });
94 actors.insert(
95 session_id.clone(),
96 ActorRegistration {
97 target: target.clone(),
98 commands: actor_tx,
99 releases: release_tx,
100 retirement: retirement_tx,
101 view: view_rx,
102 abort,
103 },
104 );
105 }
106}
107
108pub(super) async fn run_remote_session_actor(
109 session_id: String,
110 mut commands: mpsc::Receiver<ActorCommand>,
111 requests: mpsc::Sender<RemoteSessionRequest>,
112) {
113 while let Some(command) = commands.recv().await {
114 let request = match command {
115 ActorCommand::Submit {
116 queued_at: _,
117 command_id,
118 command,
119 admission,
120 reply,
121 } => RemoteSessionRequest::Submit {
122 session_id: session_id.clone(),
123 command_id,
124 command,
125 admission,
126 reply,
127 },
128 ActorCommand::Sync { reply } => RemoteSessionRequest::Sync {
129 session_id: session_id.clone(),
130 reply,
131 },
132 ActorCommand::RespondElicitation {
133 elicitation_id,
134 response,
135 reply,
136 } => RemoteSessionRequest::RespondElicitation {
137 session_id: session_id.clone(),
138 elicitation_id,
139 response,
140 reply,
141 },
142 ActorCommand::StopBackgroundTask {
143 background_task_id,
144 reply,
145 } => RemoteSessionRequest::StopBackgroundTask {
146 session_id: session_id.clone(),
147 background_task_id,
148 reply,
149 },
150 ActorCommand::Reviewer {
151 role,
152 action,
153 reply,
154 } => RemoteSessionRequest::Reviewer {
155 session_id: session_id.clone(),
156 role,
157 action,
158 reply,
159 },
160 ActorCommand::InstallPromptContext { reply, .. } => {
161 let _ = reply.send(Err(
164 "prompt context can be installed only inside the controller daemon".into(),
165 ));
166 continue;
167 }
168 ActorCommand::Lease { reply, .. } => {
169 let _ = reply.send(Err(anyhow::anyhow!(
170 "relay connection leases are available only inside the controller daemon"
171 )));
172 continue;
173 }
174 ActorCommand::RelayJob { job } => {
175 job.refuse(anyhow::anyhow!(
176 "relay connection jobs run only inside the controller daemon"
177 ));
178 continue;
179 }
180 };
181 if let Err(error) = requests.send(request).await {
182 match error.0 {
183 RemoteSessionRequest::Submit { reply, .. } => {
184 let _ = reply.send(Err("controller daemon request bridge stopped".into()));
185 }
186 RemoteSessionRequest::Sync { reply, .. }
187 | RemoteSessionRequest::RespondElicitation { reply, .. }
188 | RemoteSessionRequest::StopBackgroundTask { reply, .. } => {
189 let _ = reply.send(Err("controller daemon request bridge stopped".into()));
190 }
191 RemoteSessionRequest::Reviewer { reply, .. } => {
192 let _ = reply.send(Err("controller daemon request bridge stopped".into()));
193 }
194 }
195 break;
196 }
197 }
198}
199
200pub(super) fn spawn_remote_actor(
201 session_id: String,
202 view: ManagedSessionView,
203 requests: &mpsc::Sender<RemoteSessionRequest>,
204 actors: &mut BTreeMap<String, RemoteActorRegistration>,
205 updates: &CoalescedUpdateSender,
206) {
207 let (actor_tx, actor_rx) = mpsc::channel(32);
208 let (release_tx, _release_rx) = mpsc::unbounded_channel();
209 let (view_tx, view_rx) = watch::channel(view.clone());
210 let abort = tokio::spawn(run_remote_session_actor(
211 session_id.clone(),
212 actor_rx,
213 requests.clone(),
214 ))
215 .abort_handle();
216 actors.insert(
217 session_id.clone(),
218 RemoteActorRegistration {
219 commands: actor_tx,
220 releases: release_tx,
221 view: view_rx,
222 view_tx,
223 abort,
224 },
225 );
226 updates.send(SessionManagerUpdate { session_id, view });
227}
228
229pub fn spawn_remote_session_manager() -> Result<RemoteSessionManagerChannels> {
233 let (targets_tx, mut targets_rx) = watch::channel(Vec::<RelaySessionTarget>::new());
234 let (commands_tx, mut commands_rx) = mpsc::channel(32);
235 let (updates_tx, updates_rx) = coalesced_update_channel();
236 let (published_tx, mut published_rx) = mpsc::unbounded_channel();
237 let (requests_tx, requests_rx) = mpsc::channel(64);
238 let (shutdown_tx, mut shutdown_rx) = oneshot::channel();
239 let task = tokio::spawn(async move {
240 let mut actors = BTreeMap::<String, RemoteActorRegistration>::new();
241 let mut latest = BTreeMap::<String, ManagedSessionView>::new();
242 let mut desired = BTreeMap::<String, RelaySessionTarget>::new();
243 loop {
244 tokio::select! {
245 _ = &mut shutdown_rx => break,
246 changed = targets_rx.changed() => {
247 if changed.is_err() {
248 break;
249 }
250 desired = target_map(&targets_rx.borrow_and_update());
251 actors.retain(|session_id, actor| {
252 if desired.contains_key(session_id) {
253 true
254 } else {
255 actor.abort.abort();
256 false
257 }
258 });
259 latest.retain(|session_id, _| desired.contains_key(session_id));
265 for session_id in desired.keys() {
266 if !actors.contains_key(session_id)
267 && let Some(view) = latest.get(session_id).cloned()
268 {
269 spawn_remote_actor(
270 session_id.clone(),
271 view,
272 &requests_tx,
273 &mut actors,
274 &updates_tx,
275 );
276 }
277 }
278 }
279 command = commands_rx.recv() => {
280 let Some(ManagerCommand::Session { session_id, reply }) = command else {
281 break;
282 };
283 let handle = actors.get(&session_id).map(|actor| ManagedSessionHandle {
284 session_id: session_id.clone(),
285 commands: actor.commands.clone(),
286 releases: actor.releases.clone(),
287 view: actor.view.clone(),
288 });
289 let _ = reply.send(handle);
290 }
291 published = published_rx.recv() => {
292 let Some(RemoteManagerUpdate::Publish { session_id, view }) = published else {
293 break;
294 };
295 latest.insert(session_id.clone(), view.clone());
296 if !desired.contains_key(&session_id) {
297 continue;
298 }
299 if let Some(actor) = actors.get(&session_id) {
300 publish_view(&session_id, view, &actor.view_tx, &updates_tx);
301 continue;
302 }
303 spawn_remote_actor(
304 session_id,
305 view,
306 &requests_tx,
307 &mut actors,
308 &updates_tx,
309 );
310 }
311 }
312 }
313 for actor in actors.into_values() {
314 actor.abort.abort();
315 }
316 });
317 Ok(RemoteSessionManagerChannels {
318 targets: targets_tx,
319 control: SessionManagerControl {
320 commands: commands_tx,
321 session_cpu: watch::channel(SessionCpuTable::new()).1,
322 },
323 updates: updates_rx,
324 shutdown: SessionManagerShutdown {
325 signal: Some(shutdown_tx),
326 task: Some(task),
327 },
328 publisher: RemoteSessionPublisher {
329 updates: published_tx,
330 },
331 requests: RemoteSessionRequests {
332 requests: requests_rx,
333 },
334 })
335}