mj_controller/session_manager/
channels.rs1use super::*;
2
3#[derive(Debug, Clone)]
4pub struct SessionManagerUpdate {
5 pub session_id: String,
6 pub view: ManagedSessionView,
7}
8
9pub struct SessionManagerChannels {
10 pub targets: watch::Sender<Vec<RelaySessionTarget>>,
11 pub control: SessionManagerControl,
12 pub updates: SessionManagerUpdates,
13 pub shutdown: SessionManagerShutdown,
14}
15
16pub struct RemoteSessionManagerChannels {
22 pub targets: watch::Sender<Vec<RelaySessionTarget>>,
23 pub control: SessionManagerControl,
24 pub updates: SessionManagerUpdates,
25 pub shutdown: SessionManagerShutdown,
26 pub publisher: RemoteSessionPublisher,
27 pub requests: RemoteSessionRequests,
28}
29
30#[derive(Clone)]
31pub struct RemoteSessionPublisher {
32 pub(super) updates: mpsc::UnboundedSender<RemoteManagerUpdate>,
33}
34
35impl RemoteSessionPublisher {
36 pub async fn publish(&self, session_id: String, view: ManagedSessionView) -> Result<()> {
37 self.updates
38 .send(RemoteManagerUpdate::Publish { session_id, view })
39 .context("remote session manager stopped")
40 }
41
42 pub fn try_publish(&self, session_id: String, view: ManagedSessionView) -> Result<()> {
43 self.updates
44 .send(RemoteManagerUpdate::Publish { session_id, view })
45 .context("remote session manager update queue is unavailable")
46 }
47}
48
49pub struct RemoteSessionRequests {
50 pub(super) requests: mpsc::Receiver<RemoteSessionRequest>,
51}
52
53impl RemoteSessionRequests {
54 pub async fn recv(&mut self) -> Option<RemoteSessionRequest> {
55 self.requests.recv().await
56 }
57}
58
59pub enum RemoteSessionRequest {
60 Submit {
61 session_id: String,
62 command_id: String,
63 command: RelayCommand,
64 admission: Option<ReviewDeliveryAdmission>,
65 reply: oneshot::Sender<std::result::Result<u64, mj_client::session::SubmitFailure>>,
66 },
67 Sync {
68 session_id: String,
69 reply: oneshot::Sender<std::result::Result<(), String>>,
70 },
71 RespondElicitation {
72 session_id: String,
73 elicitation_id: String,
74 response: ElicitationResponse,
75 reply: oneshot::Sender<std::result::Result<(), String>>,
76 },
77 StopBackgroundTask {
78 session_id: String,
79 background_task_id: String,
80 reply: oneshot::Sender<std::result::Result<(), String>>,
81 },
82 Reviewer {
83 session_id: String,
84 role: Option<String>,
86 action: ReviewerAction,
87 reply: oneshot::Sender<std::result::Result<ReviewerOutcome, String>>,
88 },
89}
90
91impl RemoteSessionRequest {
92 pub fn session_id(&self) -> &str {
95 match self {
96 Self::Submit { session_id, .. }
97 | Self::Sync { session_id, .. }
98 | Self::RespondElicitation { session_id, .. }
99 | Self::StopBackgroundTask { session_id, .. }
100 | Self::Reviewer { session_id, .. } => session_id,
101 }
102 }
103}
104
105const REQUESTS_PER_STREAM: usize = 32;
107const REQUESTS_TOTAL: usize = 256;
108type ForwardRequest = std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>;
109type RequestBudgets = Arc<Mutex<std::collections::HashMap<SessionRequestStream, usize>>>;
110
111pub struct SessionRequestOrder {
117 sender: mpsc::Sender<OrderedRequest>,
118 budgets: RequestBudgets,
119 supervisor: tokio::task::JoinHandle<()>,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Hash)]
123pub(super) enum SessionRequestStream {
124 Primary(String),
125 Reviewer(String, Option<String>),
126}
127
128struct RequestPermit {
129 budgets: RequestBudgets,
130 stream: SessionRequestStream,
131}
132
133impl Drop for RequestPermit {
134 fn drop(&mut self) {
135 let mut budgets = self.budgets.lock().expect("request budgets poisoned");
136 let count = budgets.get_mut(&self.stream).expect("admitted request");
137 *count -= 1;
138 if *count == 0 {
139 budgets.remove(&self.stream);
140 }
141 }
142}
143
144struct OrderedRequest {
145 permit: RequestPermit,
146 forward: ForwardRequest,
147}
148
149impl RemoteSessionRequest {
150 pub(crate) fn reject(self, message: &str) {
151 match self {
152 Self::Submit { reply, .. } => {
153 let _ = reply.send(Err(message.to_owned().into()));
154 }
155 Self::Sync { reply, .. }
156 | Self::RespondElicitation { reply, .. }
157 | Self::StopBackgroundTask { reply, .. } => {
158 let _ = reply.send(Err(message.to_owned()));
159 }
160 Self::Reviewer { reply, .. } => {
161 let _ = reply.send(Err(message.to_owned()));
162 }
163 }
164 }
165}
166
167impl Default for SessionRequestOrder {
168 fn default() -> Self {
169 Self::new()
170 }
171}
172
173impl SessionRequestOrder {
174 #[must_use]
175 pub fn new() -> Self {
176 let (sender, receiver) = mpsc::channel(REQUESTS_TOTAL);
177 Self {
178 sender,
179 budgets: Default::default(),
180 supervisor: tokio::spawn(supervise_requests(receiver)),
181 }
182 }
183
184 pub fn dispatch<F, Fut>(&mut self, request: RemoteSessionRequest, forward: F)
186 where
187 F: FnOnce(RemoteSessionRequest) -> Fut + Send + 'static,
188 Fut: std::future::Future<Output = ()> + Send + 'static,
189 {
190 let stream = match &request {
191 RemoteSessionRequest::Reviewer {
192 session_id, role, ..
193 } => SessionRequestStream::Reviewer(session_id.clone(), role.clone()),
194 _ => SessionRequestStream::Primary(request.session_id().to_owned()),
195 };
196 let mut budgets = self.budgets.lock().expect("request budgets poisoned");
197 if budgets.get(&stream).copied().unwrap_or(0) >= REQUESTS_PER_STREAM
198 || budgets.values().sum::<usize>() >= REQUESTS_TOTAL
199 {
200 drop(budgets);
201 request.reject("session request queue is full; request was not accepted");
202 return;
203 }
204 let Ok(slot) = self.sender.try_reserve() else {
207 drop(budgets);
208 request.reject("session request dispatcher is unavailable; request was not accepted");
209 return;
210 };
211 *budgets.entry(stream.clone()).or_default() += 1;
212 drop(budgets);
213 slot.send(OrderedRequest {
214 permit: RequestPermit {
215 budgets: self.budgets.clone(),
216 stream,
217 },
218 forward: Box::pin(async move { forward(request).await }),
219 });
220 }
221
222 pub async fn drain(self) -> Result<()> {
224 drop(self.sender);
225 self.supervisor
226 .await
227 .context("session request supervisor failed")
228 }
229}
230
231async fn supervise_requests(mut requests: mpsc::Receiver<OrderedRequest>) {
232 let mut tasks = tokio::task::JoinSet::new();
233 let mut running = std::collections::HashMap::new();
234 let mut pending =
235 std::collections::HashMap::<SessionRequestStream, VecDeque<OrderedRequest>>::new();
236 let mut closed = false;
237 loop {
238 tokio::select! {
239 biased;
242 completed = tasks.join_next_with_id(), if !tasks.is_empty() => {
243 let task_id = match completed.expect("nonempty request tasks") {
244 Ok((id, ())) => id,
245 Err(error) => {
246 tracing::error!(stream = ?running.get(&error.id()), %error, "session request task failed");
247 error.id()
248 }
249 };
250 let stream = running.remove(&task_id).expect("registered request task");
251 if let Some(queue) = pending.get_mut(&stream) {
252 if let Some(request) = queue.pop_front() {
253 let handle = tasks.spawn(async move {
254 let _permit = request.permit;
255 request.forward.await;
256 });
257 running.insert(handle.id(), stream.clone());
258 }
259 if queue.is_empty() { pending.remove(&stream); }
260 }
261 }
262 request = requests.recv(), if !closed => {
263 match request {
264 Some(request) => {
265 let stream = request.permit.stream.clone();
266 if running.values().any(|active| active == &stream) {
267 pending.entry(stream).or_default().push_back(request);
268 } else {
269 let handle = tasks.spawn(async move {
270 let _permit = request.permit;
271 request.forward.await;
272 });
273 running.insert(handle.id(), stream);
274 }
275 }
276 None => closed = true,
277 }
278 }
279 }
280 if closed && tasks.is_empty() {
281 break;
282 }
283 }
284}
285
286pub struct SessionManagerShutdown {
292 pub(super) signal: Option<oneshot::Sender<()>>,
293 pub(super) task: Option<tokio::task::JoinHandle<()>>,
294}
295
296impl SessionManagerShutdown {
297 pub async fn shutdown(mut self) -> Result<()> {
298 if let Some(signal) = self.signal.take() {
299 let _ = signal.send(());
300 }
301 if let Some(task) = self.task.take() {
302 task.await.context("session manager shutdown task failed")?;
303 }
304 Ok(())
305 }
306}
307
308impl Drop for SessionManagerShutdown {
309 fn drop(&mut self) {
310 if let Some(signal) = self.signal.take() {
311 let _ = signal.send(());
312 }
313 if let Some(task) = self.task.take() {
314 task.abort();
315 }
316 }
317}
318
319#[derive(Clone)]
320pub(crate) struct CoalescedUpdateSender {
321 producers: Arc<Mutex<BTreeMap<String, Arc<()>>>>,
322 producer: Option<Arc<UpdateProducer>>,
323 pub(super) delegation: Option<DelegationSender>,
324 pub(super) observer: Option<Arc<DelegationPublisher>>,
325 pub(super) mailbox: Arc<Mutex<UpdateMailbox>>,
326 pub(super) wake: mpsc::Sender<()>,
327}
328
329struct UpdateProducer {
330 registry: Arc<Mutex<BTreeMap<String, Arc<()>>>>,
331 session_id: String,
332 identity: Arc<()>,
333}
334
335impl Drop for UpdateProducer {
336 fn drop(&mut self) {
337 let mut registry = self
338 .registry
339 .lock()
340 .expect("session producer registry poisoned");
341 if registry
342 .get(&self.session_id)
343 .is_some_and(|current| Arc::ptr_eq(current, &self.identity))
344 {
345 registry.remove(&self.session_id);
346 }
347 }
348}
349
350pub struct SessionManagerUpdates {
353 pub(super) mailbox: Arc<Mutex<UpdateMailbox>>,
354 pub(super) wake: mpsc::Receiver<()>,
355 delivered_work: Option<crate::upgrade::Work>,
358}
359
360pub(super) struct PendingUpdate {
361 update: SessionManagerUpdate,
362 work: Option<crate::upgrade::Work>,
363}
364
365#[derive(Default)]
366pub(super) struct UpdateMailbox {
367 pub(super) pending: BTreeMap<String, PendingUpdate>,
368 ready: VecDeque<String>,
369}
370
371impl UpdateMailbox {
372 fn enqueue(&mut self, pending: PendingUpdate) {
373 let session_id = pending.update.session_id.clone();
374 if self.pending.insert(session_id.clone(), pending).is_none() {
377 self.ready.push_back(session_id);
378 }
379 }
380
381 fn remove(&mut self, session_id: &str) {
382 if self.pending.remove(session_id).is_some() {
383 self.ready.retain(|queued| queued != session_id);
384 }
385 }
386
387 fn pop(&mut self) -> Option<PendingUpdate> {
388 let session_id = self.ready.pop_front()?;
389 Some(
390 self.pending
391 .remove(&session_id)
392 .expect("queued session update"),
393 )
394 }
395}
396
397impl CoalescedUpdateSender {
398 pub(super) fn for_actor(&self, session_id: &str) -> Self {
399 assert!(
400 self.producer.is_none(),
401 "only the manager registers producers"
402 );
403 let identity = Arc::new(());
404 let mut registry = self
405 .producers
406 .lock()
407 .expect("session producer registry poisoned");
408 registry.insert(session_id.to_owned(), identity.clone());
409 self.mailbox
411 .lock()
412 .expect("session update coalescer poisoned")
413 .remove(session_id);
414 let mut sender = self.clone();
415 sender.producer = Some(Arc::new(UpdateProducer {
416 registry: self.producers.clone(),
417 session_id: session_id.to_owned(),
418 identity,
419 }));
420 sender
421 }
422
423 pub(crate) fn send(&self, update: SessionManagerUpdate) {
424 let registry = self
425 .producers
426 .lock()
427 .expect("session producer registry poisoned");
428 if let Some(producer) = &self.producer {
429 assert_eq!(producer.session_id, update.session_id);
430 if !registry
431 .get(&update.session_id)
432 .is_some_and(|current| Arc::ptr_eq(current, &producer.identity))
433 {
434 return;
435 }
436 }
437 if let Some(observer) = &self.observer {
438 observer.publish(&update.view);
439 }
440 if self.wake.is_closed() {
441 return;
442 }
443 self.mailbox
444 .lock()
445 .expect("session update coalescer poisoned")
446 .enqueue(PendingUpdate {
447 update,
448 work: crate::upgrade::activity("session update").ok(),
449 });
450 let _ = self.wake.try_send(());
451 }
452}
453
454impl SessionManagerUpdates {
455 pub(super) fn pop_pending(&mut self) -> Option<SessionManagerUpdate> {
456 self.delivered_work = None;
457 let pending = self
458 .mailbox
459 .lock()
460 .expect("session update coalescer poisoned")
461 .pop()?;
462 self.delivered_work = pending.work;
463 Some(pending.update)
464 }
465
466 pub async fn recv(&mut self) -> Option<SessionManagerUpdate> {
467 loop {
468 if let Some(update) = self.pop_pending() {
469 return Some(update);
470 }
471 self.wake.recv().await?;
472 }
473 }
474
475 pub fn try_recv(
476 &mut self,
477 ) -> std::result::Result<SessionManagerUpdate, mpsc::error::TryRecvError> {
478 if let Some(update) = self.pop_pending() {
479 return Ok(update);
480 }
481 self.wake.try_recv()?;
482 self.pop_pending().ok_or(mpsc::error::TryRecvError::Empty)
483 }
484}
485
486pub(crate) fn coalesced_update_channel() -> (CoalescedUpdateSender, SessionManagerUpdates) {
487 let mailbox = Arc::new(Mutex::new(UpdateMailbox::default()));
488 let (wake_tx, wake_rx) = mpsc::channel(1);
489 (
490 CoalescedUpdateSender {
491 producers: Default::default(),
492 producer: None,
493 delegation: None,
494 observer: None,
495 mailbox: mailbox.clone(),
496 wake: wake_tx,
497 },
498 SessionManagerUpdates {
499 mailbox,
500 wake: wake_rx,
501 delivered_work: None,
502 },
503 )
504}