1use crate::deadline::instant_from_unix_ms;
2use crate::error::{ErrorKind, RpcError};
3use crate::frame::{AsyncFrameReader, write_frame};
4use crate::jsonrpc::{Envelope, Notification, Request, RequestId, Response};
5use crate::protocol::{
6 CancelParams, EmptyParams, InitializeParams, InitializeResult, Limits, Product, rpc,
7};
8use crate::{ABSOLUTE_MAX_FRAME_BYTES, Error, Result};
9use async_trait::async_trait;
10use serde_json::{Value, json};
11use std::collections::{HashMap, HashSet};
12use std::sync::Arc;
13use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
14use std::time::Duration;
15use tokio::io::{AsyncRead, AsyncWrite};
16use tokio::sync::{Mutex, OwnedSemaphorePermit, Semaphore, mpsc, oneshot};
17use tokio::task::JoinSet;
18use tokio::time::Instant;
19use tokio_util::sync::CancellationToken;
20use zeroize::Zeroizing;
21
22pub type RpcResult<T> = std::result::Result<T, RpcError>;
23const MAX_ABANDONED_CALLBACKS: usize = crate::MAX_IN_FLIGHT * 4;
24
25const COMMIT_TIMEOUT: Duration = Duration::from_secs(5);
32
33#[derive(Debug, Clone)]
34pub struct RequestContext {
35 pub request_id: RequestId,
36 pub deadline: Instant,
37 pub cancellation: CancellationToken,
38 pub peer: Peer,
41}
42
43#[derive(Debug, Clone)]
52pub struct Peer {
53 inner: Arc<PeerInner>,
54}
55
56#[derive(Debug)]
57struct PeerInner {
58 writer: mpsc::WeakSender<WriterCommand>,
63 calls: Mutex<PeerCalls>,
64 request_order: Mutex<()>,
65 next_id: AtomicU64,
66 limit: AtomicUsize,
67 semaphore: std::sync::RwLock<Arc<Semaphore>>,
68 capabilities: std::sync::RwLock<HashSet<String>>,
69}
70
71#[derive(Debug, Default)]
72struct PeerCalls {
73 pending: HashMap<RequestId, oneshot::Sender<Response>>,
74 abandoned: HashMap<RequestId, OwnedSemaphorePermit>,
79}
80
81impl Peer {
82 fn new(writer: &mpsc::Sender<WriterCommand>) -> Self {
83 Self {
84 inner: Arc::new(PeerInner {
85 writer: writer.downgrade(),
86 calls: Mutex::new(PeerCalls::default()),
87 request_order: Mutex::new(()),
88 next_id: AtomicU64::new(1),
89 limit: AtomicUsize::new(ABSOLUTE_MAX_FRAME_BYTES),
90 semaphore: std::sync::RwLock::new(Arc::new(Semaphore::new(1))),
91 capabilities: std::sync::RwLock::new(HashSet::new()),
92 }),
93 }
94 }
95
96 pub fn detached() -> Self {
104 let (writer, _) = mpsc::channel(1);
105 Self::new(&writer)
106 }
107
108 pub fn supports(&self, method: &str) -> bool {
112 self.inner
113 .capabilities
114 .read()
115 .unwrap_or_else(|poisoned| poisoned.into_inner())
116 .contains(method)
117 }
118
119 pub async fn call<P: serde::Serialize, R: serde::de::DeserializeOwned>(
125 &self,
126 method: &str,
127 params: &P,
128 context: &RequestContext,
129 ) -> RpcResult<R> {
130 if context.cancellation.is_cancelled() {
131 return Err(RpcError::new(ErrorKind::Cancelled));
132 }
133 if !self.supports(method) {
134 return Err(RpcError::new(ErrorKind::CapabilityRequired));
135 }
136 let semaphore = self
137 .inner
138 .semaphore
139 .read()
140 .unwrap_or_else(|poisoned| poisoned.into_inner())
141 .clone();
142 let permit = semaphore
143 .try_acquire_owned()
144 .map_err(|_| RpcError::unavailable(None))?;
145 let params =
146 serde_json::to_value(params).map_err(|_| RpcError::new(ErrorKind::Internal))?;
147 let deadline_unix_ms =
148 crate::deadline::clamp_unix_ms(crate::deadline::unix_ms_from_instant(context.deadline));
149 let order = tokio::select! {
150 biased;
151 _ = context.cancellation.cancelled() => return Err(RpcError::new(ErrorKind::Cancelled)),
152 order = tokio::time::timeout_at(context.deadline, self.inner.request_order.lock()) =>
153 order.map_err(|_| RpcError::new(ErrorKind::DeadlineExceeded))?,
154 };
155 let id = RequestId::new(self.inner.next_id.fetch_add(1, Ordering::Relaxed))
156 .map_err(|_| RpcError::new(ErrorKind::Internal))?;
157 let request = Request::new(id, method, deadline_unix_ms, params)
158 .map_err(|_| RpcError::new(ErrorKind::Internal))?
159 .with_parent_request_id(context.request_id);
160 let limit = self.inner.limit.load(Ordering::Acquire);
161 let payload = Zeroizing::new(
162 Envelope::Request(request)
163 .to_vec()
164 .map_err(|_| RpcError::new(ErrorKind::Internal))?,
165 );
166 if payload.is_empty() || payload.len() > limit {
167 return Err(RpcError::new(ErrorKind::MessageTooLarge));
168 }
169
170 let Some(writer) = self.inner.writer.upgrade() else {
174 return Err(RpcError::new(ErrorKind::Unavailable));
175 };
176 let (sender, receiver) = oneshot::channel();
177 self.inner.calls.lock().await.pending.insert(id, sender);
178 let command = WriterCommand {
179 payload,
180 limit,
181 committed: None,
182 };
183 let queued = tokio::select! {
184 biased;
185 _ = context.cancellation.cancelled() => {
186 self.inner.calls.lock().await.pending.remove(&id);
187 return Err(RpcError::new(ErrorKind::Cancelled));
188 }
189 _ = tokio::time::sleep_until(context.deadline) => {
190 self.inner.calls.lock().await.pending.remove(&id);
191 return Err(RpcError::new(ErrorKind::DeadlineExceeded));
192 }
193 queued = writer.send(command) => queued,
194 };
195 if queued.is_err() {
196 self.inner.calls.lock().await.pending.remove(&id);
197 return Err(RpcError::new(ErrorKind::Unavailable));
198 }
199
200 drop(order);
201
202 let response = tokio::select! {
203 biased;
204 _ = context.cancellation.cancelled() => {
205 self.abandon_call(id, permit).await;
206 return Err(RpcError::new(ErrorKind::Cancelled));
207 }
208 response = tokio::time::timeout_at(context.deadline, receiver) => response,
209 };
210 match response {
211 Ok(Ok(Response::Success(response))) => serde_json::from_value(response.result)
212 .map_err(|_| RpcError::new(ErrorKind::OperationFailed)),
213 Ok(Ok(Response::Error(response))) => Err(response.error),
214 Ok(Err(_)) => Err(RpcError::new(ErrorKind::Unavailable)),
215 Err(_) => {
216 self.abandon_call(id, permit).await;
217 Err(RpcError::new(ErrorKind::DeadlineExceeded))
218 }
219 }
220 }
221
222 async fn deliver(&self, response: Response) -> bool {
225 let Some(id) = response.id() else {
226 return false;
227 };
228 let mut calls = self.inner.calls.lock().await;
229 match calls.pending.remove(&id) {
230 Some(sender) => {
231 calls.abandoned.remove(&id);
232 let _ = sender.send(response);
233 true
234 }
235 None => calls.abandoned.remove(&id).is_some(),
236 }
237 }
238
239 async fn abandon_call(&self, id: RequestId, permit: OwnedSemaphorePermit) {
240 let mut calls = self.inner.calls.lock().await;
244 if calls.pending.remove(&id).is_some() && calls.abandoned.len() < MAX_ABANDONED_CALLBACKS {
245 calls.abandoned.insert(id, permit);
246 }
247 }
248
249 async fn fail_all(&self) {
250 let mut calls = self.inner.calls.lock().await;
251 calls.pending.clear();
252 calls.abandoned.clear();
253 }
254}
255
256#[async_trait]
259pub trait ApplicationHandler: Send + Sync + 'static {
260 fn protocol(&self) -> &'static str;
261 fn versions(&self) -> &'static [u32] {
262 &[1]
263 }
264 fn capabilities(&self) -> Vec<String>;
265
266 fn validate_capabilities(&self, _capabilities: &[String]) -> RpcResult<()> {
267 Ok(())
268 }
269
270 async fn initialize(&self, context: &RequestContext, application: Value) -> RpcResult<Value>;
271
272 async fn call(&self, context: RequestContext, method: &str, params: Value) -> RpcResult<Value>;
273
274 async fn request_finished(&self, _request_id: RequestId, _committed: bool) {}
278
279 async fn shutdown(&self) {}
280}
281
282#[derive(Debug, Clone)]
283pub struct ServerConfig {
284 pub product: Product,
285 pub limits: Limits,
286 pub startup_timeout: Duration,
287}
288
289impl Default for ServerConfig {
290 fn default() -> Self {
291 Self {
292 product: Product {
293 name: "secretspec-ipc".to_string(),
294 version: env!("CARGO_PKG_VERSION").to_string(),
295 },
296 limits: Limits {
297 max_frame_bytes: ABSOLUTE_MAX_FRAME_BYTES,
298 max_in_flight: 8,
299 },
300 startup_timeout: Duration::from_secs(5),
301 }
302 }
303}
304
305struct WriterCommand {
312 payload: Zeroizing<Vec<u8>>,
313 limit: usize,
314 committed: Option<oneshot::Sender<std::result::Result<(), ()>>>,
315}
316
317pub async fn serve<R, W, H>(
323 reader: R,
324 mut writer: W,
325 handler: Arc<H>,
326 config: ServerConfig,
327) -> Result<()>
328where
329 R: AsyncRead + Unpin + Send + 'static,
330 W: AsyncWrite + Unpin + Send + 'static,
331 H: ApplicationHandler,
332{
333 config.product.validate()?;
334 config.limits.validate()?;
335 let mut reader = AsyncFrameReader::new(reader);
336
337 let (writer_tx, mut writer_rx) =
338 mpsc::channel::<WriterCommand>(config.limits.max_in_flight + 2);
339 let disconnected = CancellationToken::new();
340 let writer_disconnected = disconnected.clone();
341 let mut writer_task = tokio::spawn(async move {
342 while let Some(command) = writer_rx.recv().await {
343 let outcome = write_frame(&mut writer, &command.payload, command.limit)
344 .await
345 .map_err(|_| ());
346 if let Some(committed) = command.committed {
347 let _ = committed.send(outcome);
348 }
349 if outcome.is_err() {
350 writer_disconnected.cancel();
351 break;
352 }
353 }
354 use tokio::io::AsyncWriteExt;
355 let _ = writer.shutdown().await;
356 });
357
358 let peer = Peer::new(&writer_tx);
359 let mut active_limit = ABSOLUTE_MAX_FRAME_BYTES;
360 let mut initialized = false;
361 let mut advertised_capabilities = HashSet::new();
362 let mut last_seen_id: Option<RequestId> = None;
363 let inflight: Arc<Mutex<HashMap<RequestId, CancellationToken>>> =
364 Arc::new(Mutex::new(HashMap::new()));
365 let mut tasks = JoinSet::new();
366 let mut semaphore = Arc::new(Semaphore::new(1));
369 let mut shutdown_request: Option<(RequestId, Instant)> = None;
370 let mut drained = false;
371 let mut fatal: Option<Error> = None;
375
376 'session: loop {
377 if shutdown_request.is_some() && tasks.is_empty() {
378 drained = true;
379 break;
380 }
381 let frame = {
384 let read = reader.read_frame(active_limit);
385 tokio::pin!(read);
386 loop {
387 tokio::select! {
388 biased;
389 _ = disconnected.cancelled() => break 'session,
390 _ = async {
391 match shutdown_request {
392 Some((_, deadline)) => tokio::time::sleep_until(deadline).await,
393 None => std::future::pending().await,
394 }
395 } => {
396 drained = true;
397 break 'session;
398 }
399 joined = tasks.join_next(), if !tasks.is_empty() => {
400 if joined.is_some_and(|result| result.is_err()) {
401 fatal = Some(Error::Protocol("application task failed"));
402 break 'session;
403 }
404 if shutdown_request.is_some() && tasks.is_empty() {
405 drained = true;
406 break 'session;
407 }
408 }
409 frame = &mut read => match frame {
410 Ok(frame) => break frame,
411 Err(error) => {
412 fatal = Some(error);
413 break 'session;
414 }
415 },
416 }
417 }
418 };
419 let Some(frame) = frame else {
420 break;
421 };
422
423 let envelope = match Envelope::parse_classified(&frame) {
424 Ok(envelope) => envelope,
425 Err((_, kind)) => {
426 let error = Response::error(None, RpcError::new(kind));
431 let _ = commit(&writer_tx, error, active_limit).await;
432 break;
433 }
434 };
435
436 match envelope {
437 Envelope::Response(response) => {
442 if !initialized || !peer.deliver(response).await {
443 break;
444 }
445 }
446 Envelope::Notification(notification) => {
447 handle_notification(notification, &inflight).await;
448 }
449 Envelope::Request(request) => {
450 if last_seen_id.is_some_and(|last| request.id <= last) {
451 let response =
452 Response::error(Some(request.id), RpcError::new(ErrorKind::InvalidRequest));
453 let _ = commit(&writer_tx, response, active_limit).await;
454 break;
455 }
456 last_seen_id = Some(request.id);
457
458 if !initialized {
459 if request.method == rpc::DISCOVER {
460 if let Err(error) = discover(
461 &request,
462 handler.as_ref(),
463 &config,
464 &writer_tx,
465 active_limit,
466 )
467 .await
468 {
469 fatal = Some(error);
470 break;
471 }
472 continue;
473 }
474 if request.method != rpc::INITIALIZE {
475 let response = Response::error(
476 Some(request.id),
477 RpcError::new(ErrorKind::InvalidRequest),
478 );
479 let _ = commit(&writer_tx, response, active_limit).await;
480 break;
481 }
482 let selected = match initialize(
483 &request,
484 handler.as_ref(),
485 &config,
486 &writer_tx,
487 &peer,
488 &mut reader,
489 )
490 .await
491 {
492 Ok(selected) => selected,
493 Err(error) => {
494 fatal = Some(error);
495 break;
496 }
497 };
498 let Some((limits, capabilities)) = selected else {
499 break;
500 };
501 peer.inner
502 .limit
503 .store(limits.max_frame_bytes, Ordering::Release);
504 *peer
505 .inner
506 .semaphore
507 .write()
508 .unwrap_or_else(|poisoned| poisoned.into_inner()) =
509 Arc::new(Semaphore::new(limits.max_in_flight));
510 active_limit = limits.max_frame_bytes;
511 semaphore = Arc::new(Semaphore::new(limits.max_in_flight));
512 advertised_capabilities = capabilities.into_iter().collect();
513 initialized = true;
514 continue;
515 }
516
517 if request.method == rpc::INITIALIZE {
518 let response =
519 Response::error(Some(request.id), RpcError::new(ErrorKind::InvalidRequest));
520 let _ = commit(&writer_tx, response, active_limit).await;
521 break;
522 }
523
524 if request.method == rpc::DISCOVER {
525 if let Err(error) = discover(
526 &request,
527 handler.as_ref(),
528 &config,
529 &writer_tx,
530 active_limit,
531 )
532 .await
533 {
534 fatal = Some(error);
535 break;
536 }
537 continue;
538 }
539
540 if shutdown_request.is_some() {
541 let response = Response::error(Some(request.id), RpcError::unavailable(None));
542 let _ = commit(&writer_tx, response, active_limit).await;
543 continue;
544 }
545
546 if request.method == rpc::SHUTDOWN {
547 if serde_json::from_value::<EmptyParams>(request.params.clone()).is_err() {
548 let response = Response::error(
549 Some(request.id),
550 RpcError::new(ErrorKind::InvalidParams),
551 );
552 let _ = commit(&writer_tx, response, active_limit).await;
553 break;
554 }
555 shutdown_request = Some((request.id, request_deadline(&request)));
556 continue;
557 }
558
559 if !request.method.starts_with("resolver.")
560 && !request.method.starts_with("provider.")
561 {
562 let response =
563 Response::error(Some(request.id), RpcError::new(ErrorKind::MethodNotFound));
564 let _ = commit(&writer_tx, response, active_limit).await;
565 continue;
566 }
567 if !advertised_capabilities.contains(&request.method) {
568 let response = Response::error(
569 Some(request.id),
570 RpcError::new(ErrorKind::CapabilityRequired),
571 );
572 let _ = commit(&writer_tx, response, active_limit).await;
573 continue;
574 }
575
576 let deadline = request_deadline(&request);
577 if deadline <= Instant::now() {
578 let response = Response::error(
579 Some(request.id),
580 RpcError::new(ErrorKind::DeadlineExceeded),
581 );
582 let _ = commit(&writer_tx, response, active_limit).await;
583 continue;
584 }
585
586 let permit = match semaphore.clone().try_acquire_owned() {
587 Ok(permit) => permit,
588 Err(_) => {
589 let response =
590 Response::error(Some(request.id), RpcError::unavailable(None));
591 let _ = commit(&writer_tx, response, active_limit).await;
592 continue;
593 }
594 };
595
596 let cancellation = CancellationToken::new();
597 inflight
598 .lock()
599 .await
600 .insert(request.id, cancellation.clone());
601 let context = RequestContext {
602 request_id: request.id,
603 deadline,
604 cancellation: cancellation.clone(),
605 peer: peer.clone(),
606 };
607 let task_handler = handler.clone();
608 let task_writer = writer_tx.clone();
609 let task_inflight = inflight.clone();
610 let task_disconnected = disconnected.clone();
611 tasks.spawn(async move {
612 let _permit = permit;
613 run_call(
614 request,
615 context,
616 task_handler,
617 task_writer,
618 task_inflight,
619 task_disconnected,
620 active_limit,
621 )
622 .await;
623 });
624 }
625 }
626 }
627
628 peer.fail_all().await;
633 for cancellation in inflight.lock().await.values() {
634 cancellation.cancel();
635 }
636 tasks.abort_all();
637 while tasks.join_next().await.is_some() {}
638 if initialized {
639 let _ = tokio::time::timeout(config.startup_timeout, handler.shutdown()).await;
641 }
642 if drained && let Some((id, deadline)) = shutdown_request {
643 let _ = tokio::time::timeout_at(
644 deadline,
645 commit(&writer_tx, Response::success(id, json!({})), active_limit),
646 )
647 .await;
648 }
649 drop(writer_tx);
650 if tokio::time::timeout(config.startup_timeout, &mut writer_task)
651 .await
652 .is_err()
653 {
654 writer_task.abort();
655 let _ = writer_task.await;
656 }
657 match fatal {
658 Some(error) => Err(error),
659 None => Ok(()),
660 }
661}
662
663async fn discover<H: ApplicationHandler>(
664 request: &Request,
665 handler: &H,
666 config: &ServerConfig,
667 writer: &mpsc::Sender<WriterCommand>,
668 limit: usize,
669) -> Result<()> {
670 let _: EmptyParams = match serde_json::from_value(request.params.clone()) {
671 Ok(params) => params,
672 Err(_) => {
673 return commit(
674 writer,
675 Response::error(Some(request.id), RpcError::new(ErrorKind::InvalidParams)),
676 limit,
677 )
678 .await;
679 }
680 };
681 if request_deadline(request) <= Instant::now() {
682 return commit(
683 writer,
684 Response::error(Some(request.id), RpcError::new(ErrorKind::DeadlineExceeded)),
685 limit,
686 )
687 .await;
688 }
689
690 let methods = handler.capabilities();
691 if crate::protocol::validate_capabilities(&methods).is_err()
692 || handler.validate_capabilities(&methods).is_err()
693 {
694 return commit(
695 writer,
696 Response::error(Some(request.id), RpcError::new(ErrorKind::Internal)),
697 limit,
698 )
699 .await;
700 }
701 let document = crate::description::openrpc(
702 handler.protocol(),
703 handler.versions(),
704 &config.product,
705 &methods,
706 )?;
707 commit(writer, Response::success(request.id, document), limit).await
708}
709
710async fn initialize<R, H: ApplicationHandler>(
711 request: &Request,
712 handler: &H,
713 config: &ServerConfig,
714 writer: &mpsc::Sender<WriterCommand>,
715 peer: &Peer,
716 reader: &mut AsyncFrameReader<R>,
717) -> Result<Option<(Limits, Vec<String>)>>
718where
719 R: AsyncRead + Unpin,
720{
721 let params: InitializeParams<Value> = match serde_json::from_value(request.params.clone()) {
722 Ok(params) => params,
723 Err(_) => {
724 let response =
725 Response::error(Some(request.id), RpcError::new(ErrorKind::InvalidParams));
726 commit(writer, response, ABSOLUTE_MAX_FRAME_BYTES).await?;
727 return Ok(None);
728 }
729 };
730 if params.protocol != handler.protocol() {
731 let response = Response::error(
732 Some(request.id),
733 RpcError::new(ErrorKind::UnsupportedVersion),
734 );
735 commit(writer, response, ABSOLUTE_MAX_FRAME_BYTES).await?;
736 return Ok(None);
737 }
738 if params.validate_common(handler.protocol()).is_err() {
739 let response = Response::error(Some(request.id), RpcError::new(ErrorKind::InvalidParams));
740 commit(writer, response, ABSOLUTE_MAX_FRAME_BYTES).await?;
741 return Ok(None);
742 }
743 let Some(version) = params
744 .versions
745 .iter()
746 .copied()
747 .filter(|version| handler.versions().contains(version))
748 .max()
749 else {
750 let response = Response::error(
751 Some(request.id),
752 RpcError::new(ErrorKind::UnsupportedVersion),
753 );
754 commit(writer, response, ABSOLUTE_MAX_FRAME_BYTES).await?;
755 return Ok(None);
756 };
757
758 let capabilities = handler.capabilities();
759 if crate::protocol::validate_capabilities(&capabilities).is_err() {
760 let response = Response::error(Some(request.id), RpcError::new(ErrorKind::Internal));
761 commit(writer, response, ABSOLUTE_MAX_FRAME_BYTES).await?;
762 return Ok(None);
763 }
764 if let Err(error) = handler.validate_capabilities(&capabilities) {
765 commit(
766 writer,
767 Response::error(Some(request.id), error),
768 ABSOLUTE_MAX_FRAME_BYTES,
769 )
770 .await?;
771 return Ok(None);
772 }
773 let limits = config.limits.select(params.limits)?;
774 *peer
778 .inner
779 .capabilities
780 .write()
781 .unwrap_or_else(|poisoned| poisoned.into_inner()) =
782 params.client_methods.iter().cloned().collect();
783 let cancellation = CancellationToken::new();
784 let context = RequestContext {
785 request_id: request.id,
786 deadline: request_deadline(request).min(Instant::now() + config.startup_timeout),
787 cancellation,
788 peer: peer.clone(),
789 };
790 if context.deadline <= Instant::now() {
791 commit(
792 writer,
793 Response::error(Some(request.id), RpcError::new(ErrorKind::DeadlineExceeded)),
794 ABSOLUTE_MAX_FRAME_BYTES,
795 )
796 .await?;
797 return Ok(None);
798 }
799 let mut operation = Box::pin(handler.initialize(&context, params.application));
805 let application = loop {
806 let outcome = tokio::select! {
807 biased;
808 _ = tokio::time::sleep_until(context.deadline) => {
809 context.cancellation.cancel();
810 commit(
811 writer,
812 Response::error(
813 Some(request.id),
814 RpcError::new(ErrorKind::DeadlineExceeded),
815 ),
816 ABSOLUTE_MAX_FRAME_BYTES,
817 )
818 .await?;
819 return Ok(None);
820 }
821 _ = context.cancellation.cancelled() => {
822 commit(
823 writer,
824 Response::error(Some(request.id), RpcError::new(ErrorKind::Cancelled)),
825 ABSOLUTE_MAX_FRAME_BYTES,
826 )
827 .await?;
828 return Ok(None);
829 }
830 outcome = &mut operation => Some(outcome),
831 frame = reader.read_frame(ABSOLUTE_MAX_FRAME_BYTES) => {
832 let Some(frame) = frame? else {
833 context.cancellation.cancel();
834 return Err(Error::Closed);
835 };
836 match Envelope::parse_classified(&frame) {
837 Ok(Envelope::Response(response)) => {
838 if !peer.deliver(response).await {
839 context.cancellation.cancel();
840 return Err(Error::Protocol(
841 "unmatched callback response during initialization",
842 ));
843 }
844 None
845 }
846 Ok(Envelope::Notification(notification)) => {
847 if notification.method == rpc::CANCEL
848 && serde_json::from_value::<CancelParams>(notification.params)
849 .is_ok_and(|params| params.id == request.id)
850 {
851 context.cancellation.cancel();
852 }
853 None
854 }
855 Ok(Envelope::Request(invalid)) => {
856 context.cancellation.cancel();
857 commit(
858 writer,
859 Response::error(
860 Some(invalid.id),
861 RpcError::new(ErrorKind::InvalidRequest),
862 ),
863 ABSOLUTE_MAX_FRAME_BYTES,
864 )
865 .await?;
866 return Ok(None);
867 }
868 Err((_, kind)) => {
869 context.cancellation.cancel();
870 commit(
871 writer,
872 Response::error(None, RpcError::new(kind)),
873 ABSOLUTE_MAX_FRAME_BYTES,
874 )
875 .await?;
876 return Ok(None);
877 }
878 }
879 }
880 };
881 let Some(outcome) = outcome else { continue };
882 break match outcome {
883 Ok(application) => application,
884 Err(error) => {
885 context.cancellation.cancel();
888 commit(
889 writer,
890 Response::error(Some(request.id), error),
891 ABSOLUTE_MAX_FRAME_BYTES,
892 )
893 .await?;
894 return Ok(None);
895 }
896 };
897 };
898 let result = InitializeResult {
899 protocol: handler.protocol().to_string(),
900 version,
901 server: config.product.clone(),
902 methods: capabilities.clone(),
903 capabilities: Default::default(),
904 limits,
905 application,
906 };
907 let value = match serde_json::to_value(result) {
908 Ok(value) => value,
909 Err(_) => {
910 context.cancellation.cancel();
911 return Err(Error::Protocol("serialize initialize result"));
912 }
913 };
914 context.cancellation.cancel();
917 commit(
918 writer,
919 Response::success(request.id, value),
920 ABSOLUTE_MAX_FRAME_BYTES,
921 )
922 .await?;
923 Ok(Some((limits, capabilities)))
924}
925
926async fn handle_notification(
927 notification: Notification,
928 inflight: &Arc<Mutex<HashMap<RequestId, CancellationToken>>>,
929) {
930 if notification.method != rpc::CANCEL {
931 return;
932 }
933 let Ok(params) = serde_json::from_value::<CancelParams>(notification.params) else {
934 return;
935 };
936 if let Some(cancellation) = inflight.lock().await.get(¶ms.id) {
937 cancellation.cancel();
938 }
939}
940
941async fn run_call<H: ApplicationHandler>(
942 request: Request,
943 context: RequestContext,
944 handler: Arc<H>,
945 writer: mpsc::Sender<WriterCommand>,
946 inflight: Arc<Mutex<HashMap<RequestId, CancellationToken>>>,
947 disconnected: CancellationToken,
948 limit: usize,
949) {
950 let mut operation = Box::pin(handler.call(context.clone(), &request.method, request.params));
951 let response = tokio::select! {
952 biased;
953 _ = tokio::time::sleep_until(context.deadline) => {
954 context.cancellation.cancel();
955 let response = Response::error(Some(request.id), RpcError::new(ErrorKind::DeadlineExceeded));
956 let _ = send_terminal(&writer, response, limit).await;
957 let _ = operation.await;
958 handler.request_finished(request.id, false).await;
959 inflight.lock().await.remove(&request.id);
960 return;
961 }
962 _ = context.cancellation.cancelled() => {
963 let response = Response::error(Some(request.id), RpcError::new(ErrorKind::Cancelled));
964 let _ = send_terminal(&writer, response, limit).await;
965 let _ = operation.await;
968 handler.request_finished(request.id, false).await;
969 inflight.lock().await.remove(&request.id);
970 return;
971 }
972 result = &mut operation => response_from_result(request.id, result),
973 };
974 let committed = commit_application_before(
975 &writer,
976 response,
977 limit,
978 context.deadline,
979 &context.cancellation,
980 &disconnected,
981 )
982 .await;
983 handler.request_finished(request.id, committed).await;
984 inflight.lock().await.remove(&request.id);
985}
986
987async fn send_terminal(
991 writer: &mpsc::Sender<WriterCommand>,
992 response: Response,
993 limit: usize,
994) -> Result<()> {
995 let payload = encode_response(&response, limit)?;
996 writer
997 .send(WriterCommand {
998 payload,
999 limit,
1000 committed: None,
1001 })
1002 .await
1003 .map_err(|_| Error::Closed)
1004}
1005
1006fn encode_response(response: &Response, limit: usize) -> Result<Zeroizing<Vec<u8>>> {
1010 let payload = serde_json::to_vec(response)
1011 .map_err(|_| Error::Protocol("failed to serialize response"))?;
1012 if !payload.is_empty() && payload.len() <= limit {
1013 return Ok(Zeroizing::new(payload));
1014 }
1015 drop(Zeroizing::new(payload));
1016 let replacement = Response::error(response.id(), RpcError::new(ErrorKind::MessageTooLarge));
1017 serde_json::to_vec(&replacement)
1018 .map(Zeroizing::new)
1019 .map_err(|_| Error::Protocol("failed to serialize response"))
1020}
1021
1022fn response_from_result(id: RequestId, result: RpcResult<Value>) -> Response {
1023 match result {
1024 Ok(value) => Response::success(id, value),
1025 Err(error) => Response::error(Some(id), error),
1026 }
1027}
1028
1029async fn commit_application_before(
1030 writer: &mpsc::Sender<WriterCommand>,
1031 response: Response,
1032 limit: usize,
1033 deadline: Instant,
1034 cancellation: &CancellationToken,
1035 disconnected: &CancellationToken,
1036) -> bool {
1037 let encoded = serde_json::to_vec(&response).map(Zeroizing::new).ok();
1038 let application_response = encoded
1039 .as_ref()
1040 .is_some_and(|payload| !payload.is_empty() && payload.len() <= limit);
1041 let request_id = response.id();
1042 let payload = match encoded.filter(|_| application_response) {
1043 Some(payload) => payload,
1044 None => {
1045 let replacement =
1046 Response::error(request_id, RpcError::new(ErrorKind::MessageTooLarge));
1047 match serde_json::to_vec(&replacement).map(Zeroizing::new) {
1048 Ok(payload) => payload,
1049 Err(_) => return false,
1050 }
1051 }
1052 };
1053 let (committed_tx, committed_rx) = oneshot::channel();
1054 let command = WriterCommand {
1055 payload,
1056 limit,
1057 committed: Some(committed_tx),
1058 };
1059
1060 let enqueued = tokio::select! {
1065 biased;
1066 _ = tokio::time::sleep_until(deadline) => {
1067 cancellation.cancel();
1068 let response = Response::error(request_id, RpcError::new(ErrorKind::DeadlineExceeded));
1069 let _ = send_terminal(writer, response, limit).await;
1070 false
1071 }
1072 _ = cancellation.cancelled() => {
1073 let response = Response::error(request_id, RpcError::new(ErrorKind::Cancelled));
1074 let _ = send_terminal(writer, response, limit).await;
1075 false
1076 }
1077 result = writer.send(command) => result.is_ok(),
1078 };
1079 if !enqueued {
1080 return false;
1081 }
1082
1083 cancellation.cancel();
1087
1088 match tokio::time::timeout_at(deadline, committed_rx).await {
1089 Ok(Ok(Ok(()))) => application_response,
1090 Ok(Ok(Err(()))) | Ok(Err(_)) | Err(_) => {
1091 disconnected.cancel();
1092 false
1093 }
1094 }
1095}
1096
1097async fn commit(
1098 writer: &mpsc::Sender<WriterCommand>,
1099 response: Response,
1100 limit: usize,
1101) -> Result<()> {
1102 let payload = encode_response(&response, limit)?;
1103 tokio::time::timeout(COMMIT_TIMEOUT, async {
1104 let (committed_tx, committed_rx) = oneshot::channel();
1105 writer
1106 .send(WriterCommand {
1107 payload,
1108 limit,
1109 committed: Some(committed_tx),
1110 })
1111 .await
1112 .map_err(|_| Error::Closed)?;
1113 committed_rx
1114 .await
1115 .map_err(|_| Error::Closed)?
1116 .map_err(|_| Error::Closed)
1117 })
1118 .await
1119 .map_err(|_| Error::DeadlineExceeded)?
1120}
1121
1122fn request_deadline(request: &Request) -> Instant {
1123 instant_from_unix_ms(request.deadline_unix_ms())
1124}
1125
1126#[cfg(test)]
1127mod tests {
1128 use super::*;
1129
1130 #[tokio::test]
1135 async fn a_peer_does_not_keep_the_writer_channel_open() {
1136 let (writer_tx, mut writer_rx) = mpsc::channel::<WriterCommand>(1);
1137 let peer = Peer::new(&writer_tx);
1138 peer.inner
1139 .capabilities
1140 .write()
1141 .unwrap()
1142 .insert("client.prompt".to_string());
1143 drop(writer_tx);
1144 assert!(
1145 writer_rx.recv().await.is_none(),
1146 "the peer kept the writer channel open after the session dropped its sender"
1147 );
1148
1149 let context = RequestContext {
1152 request_id: RequestId::new(1).unwrap(),
1153 deadline: Instant::now() + Duration::from_secs(30),
1154 cancellation: CancellationToken::new(),
1155 peer: peer.clone(),
1156 };
1157 let error = peer
1158 .call::<_, Value>("client.prompt", &json!({}), &context)
1159 .await
1160 .unwrap_err();
1161 assert_eq!(error.data.kind, ErrorKind::Unavailable);
1162 }
1163
1164 #[tokio::test]
1165 async fn callbacks_obey_the_pre_negotiation_outbound_limit() {
1166 let (writer_tx, mut writer_rx) = mpsc::channel::<WriterCommand>(4);
1167 let peer = Peer::new(&writer_tx);
1168 peer.inner
1169 .capabilities
1170 .write()
1171 .unwrap()
1172 .insert("client.prompt".to_string());
1173 let cancellation = CancellationToken::new();
1174 let context = RequestContext {
1175 request_id: RequestId::new(1).unwrap(),
1176 deadline: Instant::now() + Duration::from_secs(2),
1177 cancellation: cancellation.clone(),
1178 peer: peer.clone(),
1179 };
1180
1181 let first_peer = peer.clone();
1182 let first_context = context.clone();
1183 let first = tokio::spawn(async move {
1184 first_peer
1185 .call::<_, Value>("client.prompt", &json!({}), &first_context)
1186 .await
1187 });
1188 let _first_frame = writer_rx.recv().await.expect("first callback frame");
1189
1190 let second = peer
1191 .call::<_, Value>("client.prompt", &json!({}), &context)
1192 .await;
1193 assert!(matches!(
1194 second,
1195 Err(ref error) if error.data.kind == ErrorKind::Unavailable
1196 ));
1197 assert!(writer_rx.try_recv().is_err(), "a second callback was sent");
1198
1199 cancellation.cancel();
1200 assert!(matches!(
1201 first.await.unwrap(),
1202 Err(ref error) if error.data.kind == ErrorKind::Cancelled
1203 ));
1204 }
1205
1206 #[tokio::test]
1207 async fn pre_negotiation_callbacks_use_the_absolute_frame_limit() {
1208 let (writer_tx, mut writer_rx) = mpsc::channel::<WriterCommand>(2);
1209 let peer = Peer::new(&writer_tx);
1210 peer.inner
1211 .capabilities
1212 .write()
1213 .unwrap()
1214 .insert("client.prompt".to_string());
1215 let context = RequestContext {
1216 request_id: RequestId::new(1).unwrap(),
1217 deadline: Instant::now() + Duration::from_secs(2),
1218 cancellation: CancellationToken::new(),
1219 peer: peer.clone(),
1220 };
1221
1222 let outcome = peer
1223 .call::<_, Value>(
1224 "client.prompt",
1225 &json!({"value": "x".repeat(ABSOLUTE_MAX_FRAME_BYTES)}),
1226 &context,
1227 )
1228 .await;
1229 assert!(matches!(
1230 outcome,
1231 Err(ref error) if error.data.kind == ErrorKind::MessageTooLarge
1232 ));
1233 assert!(writer_rx.try_recv().is_err(), "oversized callback was sent");
1234 }
1235
1236 #[tokio::test]
1237 async fn callbacks_switch_to_the_negotiated_outbound_limit() {
1238 let (writer_tx, mut writer_rx) = mpsc::channel::<WriterCommand>(6);
1239 let peer = Peer::new(&writer_tx);
1240 peer.inner
1241 .capabilities
1242 .write()
1243 .unwrap()
1244 .insert("client.prompt".to_string());
1245 *peer.inner.semaphore.write().unwrap() = Arc::new(Semaphore::new(2));
1246 peer.inner.limit.store(32 * 1024, Ordering::Release);
1247 let cancellation = CancellationToken::new();
1248 let context = RequestContext {
1249 request_id: RequestId::new(1).unwrap(),
1250 deadline: Instant::now() + Duration::from_secs(2),
1251 cancellation: cancellation.clone(),
1252 peer: peer.clone(),
1253 };
1254
1255 let mut calls = Vec::new();
1256 for _ in 0..2 {
1257 let peer = peer.clone();
1258 let context = context.clone();
1259 calls.push(tokio::spawn(async move {
1260 peer.call::<_, Value>("client.prompt", &json!({}), &context)
1261 .await
1262 }));
1263 writer_rx.recv().await.expect("negotiated callback frame");
1264 }
1265 let third = peer
1266 .call::<_, Value>("client.prompt", &json!({}), &context)
1267 .await;
1268 assert!(matches!(
1269 third,
1270 Err(ref error) if error.data.kind == ErrorKind::Unavailable
1271 ));
1272
1273 cancellation.cancel();
1274 for call in calls {
1275 assert!(matches!(
1276 call.await.unwrap(),
1277 Err(ref error) if error.data.kind == ErrorKind::Cancelled
1278 ));
1279 }
1280 }
1281
1282 #[tokio::test]
1283 async fn a_callback_cannot_start_after_its_parent_is_terminal() {
1284 let (writer_tx, mut writer_rx) = mpsc::channel::<WriterCommand>(2);
1285 let peer = Peer::new(&writer_tx);
1286 peer.inner
1287 .capabilities
1288 .write()
1289 .unwrap()
1290 .insert("client.prompt".to_string());
1291 let cancellation = CancellationToken::new();
1292 cancellation.cancel();
1293 let context = RequestContext {
1294 request_id: RequestId::new(1).unwrap(),
1295 deadline: Instant::now() + Duration::from_secs(2),
1296 cancellation,
1297 peer: peer.clone(),
1298 };
1299
1300 let outcome = peer
1301 .call::<_, Value>("client.prompt", &json!({}), &context)
1302 .await;
1303 assert!(matches!(
1304 outcome,
1305 Err(ref error) if error.data.kind == ErrorKind::Cancelled
1306 ));
1307 assert!(writer_rx.try_recv().is_err(), "late callback was sent");
1308 }
1309
1310 #[test]
1313 fn expired_deadline_is_not_started() {
1314 let request = Request::new(RequestId::new(1).unwrap(), "test.call", 1, json!({})).unwrap();
1315 assert!(request_deadline(&request) <= Instant::now());
1316 }
1317
1318 #[tokio::test]
1319 async fn an_expired_callback_consumes_its_terminal_response() {
1320 let (writer_tx, mut writer_rx) = mpsc::channel::<WriterCommand>(1);
1321 let peer = Peer::new(&writer_tx);
1322 peer.inner
1323 .capabilities
1324 .write()
1325 .unwrap()
1326 .insert("client.prompt".to_string());
1327
1328 for raw_id in 1..=64 {
1331 let context = RequestContext {
1332 request_id: RequestId::new(10).unwrap(),
1333 deadline: Instant::now(),
1334 cancellation: CancellationToken::new(),
1335 peer: peer.clone(),
1336 };
1337 let error = peer
1338 .call::<_, Value>("client.prompt", &json!({}), &context)
1339 .await
1340 .unwrap_err();
1341 assert_eq!(error.data.kind, ErrorKind::DeadlineExceeded);
1342 let _request = writer_rx.recv().await.unwrap();
1343
1344 let id = RequestId::new(raw_id).unwrap();
1345 let terminal = Response::error(Some(id), RpcError::new(ErrorKind::DeadlineExceeded));
1346 assert!(peer.deliver(terminal.clone()).await);
1347 assert!(!peer.deliver(terminal).await, "duplicates remain invalid");
1348 }
1349 assert!(peer.inner.calls.lock().await.abandoned.is_empty());
1350 }
1351
1352 #[tokio::test]
1353 async fn an_abandoned_callback_keeps_its_slot_until_the_client_answers() {
1354 let (writer_tx, mut writer_rx) = mpsc::channel::<WriterCommand>(4);
1355 let peer = Peer::new(&writer_tx);
1356 peer.inner
1357 .capabilities
1358 .write()
1359 .unwrap()
1360 .insert("client.prompt".to_string());
1361 let context = |deadline| RequestContext {
1362 request_id: RequestId::new(10).unwrap(),
1363 deadline,
1364 cancellation: CancellationToken::new(),
1365 peer: peer.clone(),
1366 };
1367
1368 let error = peer
1369 .call::<_, Value>("client.prompt", &json!({}), &context(Instant::now()))
1370 .await
1371 .unwrap_err();
1372 assert_eq!(error.data.kind, ErrorKind::DeadlineExceeded);
1373 let _request = writer_rx.recv().await.unwrap();
1374
1375 let error = peer
1378 .call::<_, Value>(
1379 "client.prompt",
1380 &json!({}),
1381 &context(Instant::now() + Duration::from_secs(300)),
1382 )
1383 .await
1384 .unwrap_err();
1385 assert_eq!(error.data.kind, ErrorKind::Unavailable);
1386
1387 let terminal = Response::error(
1388 Some(RequestId::new(1).unwrap()),
1389 RpcError::new(ErrorKind::DeadlineExceeded),
1390 );
1391 assert!(peer.deliver(terminal).await);
1392 let error = peer
1393 .call::<_, Value>("client.prompt", &json!({}), &context(Instant::now()))
1394 .await
1395 .unwrap_err();
1396 assert_eq!(error.data.kind, ErrorKind::DeadlineExceeded);
1397 let _request = writer_rx.recv().await.unwrap();
1398 }
1399
1400 #[tokio::test]
1401 async fn a_cancelled_callback_also_consumes_its_terminal_response() {
1402 let (writer_tx, mut writer_rx) = mpsc::channel::<WriterCommand>(1);
1403 let peer = Peer::new(&writer_tx);
1404 peer.inner
1405 .capabilities
1406 .write()
1407 .unwrap()
1408 .insert("client.prompt".to_string());
1409 let cancellation = CancellationToken::new();
1410 let context = RequestContext {
1411 request_id: RequestId::new(10).unwrap(),
1412 deadline: Instant::now() + Duration::from_secs(1),
1413 cancellation: cancellation.clone(),
1414 peer: peer.clone(),
1415 };
1416
1417 let call_peer = peer.clone();
1418 let call = tokio::spawn(async move {
1419 call_peer
1420 .call::<_, Value>("client.prompt", &json!({}), &context)
1421 .await
1422 });
1423 let _request = writer_rx.recv().await.unwrap();
1424 cancellation.cancel();
1425 let error = call.await.unwrap().unwrap_err();
1426 assert_eq!(error.data.kind, ErrorKind::Cancelled);
1427 let terminal = Response::error(
1428 Some(RequestId::new(1).unwrap()),
1429 RpcError::new(ErrorKind::Cancelled),
1430 );
1431 assert!(peer.deliver(terminal).await);
1432 assert!(peer.inner.calls.lock().await.abandoned.is_empty());
1433 }
1434}