1use crate::client::{CallbackHandler, Client};
2use crate::connection::{FilesystemAccess, SshOptions};
3use crate::deadline::instant_from_unix_ms;
4use crate::error::{ErrorKind, RpcError};
5use crate::protocol::callback;
6use crate::protocol::provider::{
7 self as provider_protocol, InitializeApplication as ProviderInitializeApplication,
8 InitializedApplication as ProviderInitializedApplication, Metadata,
9};
10use crate::protocol::resolver::{
11 self as resolver_protocol, InitializeApplication as ResolverInitializeApplication,
12 InitializedApplication as ResolverInitializedApplication,
13};
14use crate::protocol::{
15 InitializeParams, InitializeResult, Limits, PROTOCOL_VERSION, PROVIDER_PROTOCOL, Product,
16 RESOLVER_PROTOCOL,
17};
18use crate::{Error, Result, deadline_unix_ms_after};
19use serde::Serialize;
20use serde::de::DeserializeOwned;
21use std::collections::HashSet;
22use std::process::Stdio;
23use std::sync::Arc;
24use std::sync::atomic::{AtomicBool, Ordering};
25use std::time::Duration;
26use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite};
27use tokio::process::{Child, Command};
28use tokio::sync::Mutex;
29use tokio::task::JoinHandle;
30use tokio::time::Instant;
31use tokio_util::sync::CancellationToken;
32use zeroize::Zeroizing;
33
34const REAP_GRACE: Duration = Duration::from_secs(2);
39
40pub use crate::launch::{Environment, LaunchOptions};
41
42pub struct ChildSession {
44 client: Client,
45 child: Arc<Mutex<Child>>,
46 monitor_cancel: CancellationToken,
47 monitor: Mutex<Option<JoinHandle<()>>>,
48 stderr: Mutex<Option<JoinHandle<Zeroizing<Vec<u8>>>>>,
49 closed: AtomicBool,
50}
51
52impl ChildSession {
53 pub fn client(&self) -> &Client {
54 &self.client
55 }
56
57 pub async fn close(&self, deadline_unix_ms: u64) -> Result<()> {
58 if self.closed.swap(true, Ordering::AcqRel) {
59 return Ok(());
60 }
61 let protocol_outcome = self.client.close(deadline_unix_ms).await;
62 self.monitor_cancel.cancel();
63 if let Some(monitor) = self.monitor.lock().await.take() {
64 let _ = monitor.await;
65 }
66
67 let requested = instant_from_unix_ms(deadline_unix_ms);
68 let cap = Instant::now() + Duration::from_secs(5);
69 let deadline = requested.min(cap);
70 let wait_outcome = wait_until(&self.child, deadline).await;
71 let mut kill_error = None;
72 if !matches!(&wait_outcome, Ok(true)) {
73 let kill_deadline = Instant::now() + REAP_GRACE;
78 let mut child = self.child.lock().await;
79 if let Err(error) = child.start_kill() {
80 kill_error = Some(error);
81 } else {
82 let _ = tokio::time::timeout_at(kill_deadline, child.wait()).await;
83 }
84 }
85 if let Some(stderr) = self.stderr.lock().await.take() {
86 finish_stderr(stderr, Instant::now() + REAP_GRACE).await;
89 }
90 wait_outcome?;
91 if let Some(error) = kill_error {
92 return Err(Error::Io(error));
93 }
94 protocol_outcome
95 }
96}
97
98pub struct ProviderSession {
101 child: ChildSession,
102 capabilities: HashSet<String>,
103 metadata: Metadata,
104}
105
106#[async_trait::async_trait]
114pub trait CredentialResponder: Send + Sync + 'static {
115 async fn credential(
116 &self,
117 params: callback::CredentialParams,
118 ) -> std::result::Result<callback::CredentialResult, RpcError>;
119}
120
121struct CredentialCallbacks {
122 responder: Arc<dyn CredentialResponder>,
123}
124
125#[async_trait::async_trait]
126impl CallbackHandler for CredentialCallbacks {
127 async fn call(
128 &self,
129 method: &str,
130 params: serde_json::Value,
131 ) -> std::result::Result<serde_json::Value, RpcError> {
132 if method != callback::method::CREDENTIAL {
133 return Err(RpcError::new(ErrorKind::MethodNotFound));
134 }
135 let params: callback::CredentialParams =
136 serde_json::from_value(params).map_err(|_| RpcError::new(ErrorKind::InvalidParams))?;
137 params
138 .validate()
139 .map_err(|_| RpcError::new(ErrorKind::InvalidParams))?;
140 let result = self.responder.credential(params).await?;
141 result
142 .validate()
143 .map_err(|_| RpcError::new(ErrorKind::InvalidParams))?;
144 serde_json::to_value(result).map_err(|_| RpcError::new(ErrorKind::Internal))
145 }
146}
147
148macro_rules! provider_calls {
149 ($(($name:ident, $method:ty)),+ $(,)?) => {
150 $(
151 pub async fn $name(
152 &self,
153 params: &<$method as provider_protocol::method::Method>::Params,
154 deadline_unix_ms: u64,
155 ) -> Result<<$method as provider_protocol::method::Method>::Result> {
156 self.execute::<$method>(params, deadline_unix_ms).await
157 }
158 )+
159 };
160}
161
162impl ProviderSession {
163 pub async fn launch(
166 options: LaunchOptions,
167 client: Product,
168 limits: Limits,
169 application: ProviderInitializeApplication,
170 startup_deadline_unix_ms: u64,
171 ) -> Result<Self> {
172 Self::launch_with_credential_broker(
173 options,
174 client,
175 limits,
176 application,
177 startup_deadline_unix_ms,
178 None,
179 )
180 .await
181 }
182
183 pub async fn launch_with_credential_broker(
189 options: LaunchOptions,
190 client: Product,
191 limits: Limits,
192 application: ProviderInitializeApplication,
193 startup_deadline_unix_ms: u64,
194 responder: Option<Arc<dyn CredentialResponder>>,
195 ) -> Result<Self> {
196 application.validate()?;
197 let expected_scheme = application.scheme.clone();
198 let (client_methods, callbacks): (Vec<String>, Option<Arc<dyn CallbackHandler>>) =
199 match responder {
200 Some(responder) => (
201 vec![callback::method::CREDENTIAL.to_string()],
202 Some(Arc::new(CredentialCallbacks { responder })),
203 ),
204 None => (Vec::new(), None),
205 };
206 let initialize = InitializeParams {
207 protocol: PROVIDER_PROTOCOL.to_string(),
208 versions: vec![PROTOCOL_VERSION],
209 client,
210 limits,
211 client_methods,
212 application,
213 };
214 let (child, initialized) = spawn_with_callbacks::<_, ProviderInitializedApplication>(
215 options,
216 initialize,
217 startup_deadline_unix_ms,
218 callbacks,
219 )
220 .await?;
221 let validation = initialized
222 .application
223 .provider
224 .validate()
225 .and_then(|_| provider_protocol::validate_capabilities(&initialized.methods))
226 .and_then(|_| {
227 if initialized.application.provider.name == expected_scheme {
228 Ok(())
229 } else {
230 Err(Error::Protocol(
231 "provider metadata name does not match its scheme",
232 ))
233 }
234 });
235 if let Err(error) = validation {
236 let _ = child
237 .close(deadline_unix_ms_after(Duration::from_secs(1)))
238 .await;
239 return Err(error);
240 }
241 Ok(Self {
242 child,
243 capabilities: initialized.methods.into_iter().collect(),
244 metadata: initialized.application.provider,
245 })
246 }
247
248 pub fn raw(&self) -> &Client {
249 self.child.client()
250 }
251
252 pub async fn execute<M>(&self, params: &M::Params, deadline_unix_ms: u64) -> Result<M::Result>
253 where
254 M: provider_protocol::method::Method,
255 {
256 self.call(M::NAME, params, deadline_unix_ms).await
257 }
258
259 pub async fn call<P: Serialize, R: DeserializeOwned>(
260 &self,
261 method: &str,
262 params: &P,
263 deadline_unix_ms: u64,
264 ) -> Result<R> {
265 self.child
266 .client()
267 .call(method, params, deadline_unix_ms)
268 .await
269 }
270
271 provider_calls!(
272 (resolve_address, provider_protocol::method::ResolveAddress),
273 (get, provider_protocol::method::Get),
274 (get_many, provider_protocol::method::GetMany),
275 (exists, provider_protocol::method::Exists),
276 (set, provider_protocol::method::Set),
277 (set_expiring, provider_protocol::method::SetExpiring),
278 (delete, provider_protocol::method::Delete),
279 (clear, provider_protocol::method::Clear),
280 (
281 describe_write_target,
282 provider_protocol::method::DescribeWriteTarget
283 ),
284 (reflect, provider_protocol::method::Reflect),
285 );
286
287 pub async fn check_writable(
288 &self,
289 params: &provider_protocol::AddressParams,
290 deadline_unix_ms: u64,
291 ) -> Result<()> {
292 self.execute::<provider_protocol::method::CheckWritable>(params, deadline_unix_ms)
293 .await
294 .map(|_| ())
295 }
296
297 pub async fn check_deletable(
298 &self,
299 params: &provider_protocol::AddressParams,
300 deadline_unix_ms: u64,
301 ) -> Result<()> {
302 self.execute::<provider_protocol::method::CheckDeletable>(params, deadline_unix_ms)
303 .await
304 .map(|_| ())
305 }
306
307 pub fn capabilities(&self) -> &HashSet<String> {
308 &self.capabilities
309 }
310
311 pub fn supports(&self, method: &str) -> bool {
312 self.capabilities.contains(method)
313 }
314
315 pub fn metadata(&self) -> &Metadata {
316 &self.metadata
317 }
318
319 pub fn is_closed(&self) -> bool {
320 self.child.client().is_closed()
321 }
322
323 pub async fn close(&self, deadline_unix_ms: u64) -> Result<()> {
324 self.child.close(deadline_unix_ms).await
325 }
326}
327
328pub struct ResolverSession {
334 transport: ResolverTransport,
335 filesystem: FilesystemAccess,
336 capabilities: HashSet<String>,
337 initialized: ResolverInitializedApplication,
338}
339
340enum ResolverTransport {
341 Child(ChildSession),
342 Connected(Client),
343}
344
345impl ResolverTransport {
346 fn client(&self) -> &Client {
347 match self {
348 Self::Child(child) => child.client(),
349 Self::Connected(client) => client,
350 }
351 }
352
353 async fn close(&self, deadline_unix_ms: u64) -> Result<()> {
354 match self {
355 Self::Child(child) => child.close(deadline_unix_ms).await,
356 Self::Connected(client) => client.close(deadline_unix_ms).await,
357 }
358 }
359}
360
361#[async_trait::async_trait]
373pub trait PromptResponder: Send + Sync + 'static {
374 async fn prompt(
375 &self,
376 params: callback::PromptParams,
377 ) -> std::result::Result<callback::PromptResult, RpcError>;
378}
379
380struct PromptCallbacks {
381 responder: Arc<dyn PromptResponder>,
382}
383
384#[async_trait::async_trait]
385impl CallbackHandler for PromptCallbacks {
386 async fn call(
387 &self,
388 method: &str,
389 params: serde_json::Value,
390 ) -> std::result::Result<serde_json::Value, RpcError> {
391 if method != callback::method::PROMPT {
392 return Err(RpcError::new(ErrorKind::MethodNotFound));
393 }
394 let params: callback::PromptParams =
395 serde_json::from_value(params).map_err(|_| RpcError::new(ErrorKind::InvalidParams))?;
396 params
397 .validate()
398 .map_err(|_| RpcError::new(ErrorKind::InvalidParams))?;
399 let result = self.responder.prompt(params).await?;
400 result
403 .validate()
404 .map_err(|_| RpcError::new(ErrorKind::InvalidParams))?;
405 serde_json::to_value(result).map_err(|_| RpcError::new(ErrorKind::Internal))
406 }
407}
408
409impl ResolverSession {
410 pub async fn launch(
411 options: LaunchOptions,
412 client: Product,
413 limits: Limits,
414 application: ResolverInitializeApplication,
415 startup_deadline_unix_ms: u64,
416 ) -> Result<Self> {
417 Self::launch_with_prompt(
418 options,
419 client,
420 limits,
421 application,
422 startup_deadline_unix_ms,
423 None,
424 )
425 .await
426 }
427
428 pub async fn launch_with_prompt(
431 options: LaunchOptions,
432 client: Product,
433 limits: Limits,
434 application: ResolverInitializeApplication,
435 startup_deadline_unix_ms: u64,
436 responder: Option<Arc<dyn PromptResponder>>,
437 ) -> Result<Self> {
438 application.validate_for_connection()?;
439 let (client_methods, callbacks): (Vec<String>, Option<Arc<dyn CallbackHandler>>) =
440 match responder {
441 Some(responder) => (
442 vec![callback::method::PROMPT.to_string()],
443 Some(Arc::new(PromptCallbacks { responder })),
444 ),
445 None => (Vec::new(), None),
446 };
447 let initialize = InitializeParams {
448 protocol: RESOLVER_PROTOCOL.to_string(),
449 versions: vec![PROTOCOL_VERSION],
450 client,
451 limits,
452 client_methods,
453 application,
454 };
455 let (child, initialized) = spawn_with_callbacks::<_, ResolverInitializedApplication>(
456 options,
457 initialize,
458 startup_deadline_unix_ms,
459 callbacks,
460 )
461 .await?;
462 Self::from_transport(
463 ResolverTransport::Child(child),
464 initialized,
465 FilesystemAccess::Shared,
466 )
467 .await
468 }
469
470 pub async fn connect<R, W>(
477 reader: R,
478 writer: W,
479 client: Product,
480 limits: Limits,
481 application: ResolverInitializeApplication,
482 filesystem: FilesystemAccess,
483 startup_deadline_unix_ms: u64,
484 ) -> Result<Self>
485 where
486 R: AsyncRead + Unpin + Send + 'static,
487 W: AsyncWrite + Unpin + Send + 'static,
488 {
489 Self::connect_with_prompt(
490 reader,
491 writer,
492 client,
493 limits,
494 application,
495 filesystem,
496 startup_deadline_unix_ms,
497 None,
498 )
499 .await
500 }
501
502 #[allow(clippy::too_many_arguments)]
504 pub async fn connect_with_prompt<R, W>(
505 reader: R,
506 writer: W,
507 client: Product,
508 limits: Limits,
509 application: ResolverInitializeApplication,
510 filesystem: FilesystemAccess,
511 startup_deadline_unix_ms: u64,
512 responder: Option<Arc<dyn PromptResponder>>,
513 ) -> Result<Self>
514 where
515 R: AsyncRead + Unpin + Send + 'static,
516 W: AsyncWrite + Unpin + Send + 'static,
517 {
518 application.validate_for_connection()?;
519 let (client_methods, callbacks): (Vec<String>, Option<Arc<dyn CallbackHandler>>) =
520 match responder {
521 Some(responder) => (
522 vec![callback::method::PROMPT.to_string()],
523 Some(Arc::new(PromptCallbacks { responder })),
524 ),
525 None => (Vec::new(), None),
526 };
527 let initialize = InitializeParams {
528 protocol: RESOLVER_PROTOCOL.to_string(),
529 versions: vec![PROTOCOL_VERSION],
530 client,
531 limits,
532 client_methods,
533 application,
534 };
535 let (client, initialized) = Client::connect_with_callbacks(
536 reader,
537 writer,
538 initialize,
539 startup_deadline_unix_ms,
540 callbacks,
541 )
542 .await?;
543 Self::from_transport(
544 ResolverTransport::Connected(client),
545 initialized,
546 filesystem,
547 )
548 .await
549 }
550
551 pub async fn launch_ssh(
554 options: SshOptions,
555 client: Product,
556 limits: Limits,
557 application: ResolverInitializeApplication,
558 startup_deadline_unix_ms: u64,
559 ) -> Result<Self> {
560 Self::launch_ssh_with_prompt(
561 options,
562 client,
563 limits,
564 application,
565 startup_deadline_unix_ms,
566 None,
567 )
568 .await
569 }
570
571 pub async fn launch_ssh_with_prompt(
573 options: SshOptions,
574 client: Product,
575 limits: Limits,
576 application: ResolverInitializeApplication,
577 startup_deadline_unix_ms: u64,
578 responder: Option<Arc<dyn PromptResponder>>,
579 ) -> Result<Self> {
580 let mut session = Self::launch_with_prompt(
581 options.launch_options()?,
582 client,
583 limits,
584 application,
585 startup_deadline_unix_ms,
586 responder,
587 )
588 .await?;
589 session.filesystem = options.filesystem;
590 Ok(session)
591 }
592
593 async fn from_transport(
594 transport: ResolverTransport,
595 initialized: InitializeResult<ResolverInitializedApplication>,
596 filesystem: FilesystemAccess,
597 ) -> Result<Self> {
598 if let Err(error) = initialized
599 .application
600 .validate()
601 .and_then(|_| resolver_protocol::validate_capabilities(&initialized.methods))
602 {
603 let _ = transport
604 .close(deadline_unix_ms_after(Duration::from_secs(1)))
605 .await;
606 return Err(error);
607 }
608 Ok(Self {
609 transport,
610 filesystem,
611 capabilities: initialized.methods.into_iter().collect(),
612 initialized: initialized.application,
613 })
614 }
615
616 pub fn raw(&self) -> &Client {
619 self.transport.client()
620 }
621
622 pub async fn get(
623 &self,
624 params: &resolver_protocol::GetParams,
625 deadline_unix_ms: u64,
626 ) -> Result<resolver_protocol::GetResult> {
627 let params = self.filesystem.prepare_get(params)?;
628 let result = self
629 .transport
630 .client()
631 .call(
632 resolver_protocol::method::GET,
633 params.as_ref(),
634 deadline_unix_ms,
635 )
636 .await?;
637 if !self.filesystem.accepts(&result) {
638 let _ = self.close(deadline_unix_ms).await;
641 return Err(Error::Protocol(
642 "resolver returned a path on a remote filesystem",
643 ));
644 }
645 Ok(result)
646 }
647
648 pub async fn release(
649 &self,
650 params: &resolver_protocol::ReleaseParams,
651 deadline_unix_ms: u64,
652 ) -> Result<resolver_protocol::ReleaseResult> {
653 self.transport
654 .client()
655 .call(resolver_protocol::method::RELEASE, params, deadline_unix_ms)
656 .await
657 }
658
659 pub async fn set(
663 &self,
664 params: &resolver_protocol::SetParams,
665 deadline_unix_ms: u64,
666 ) -> Result<resolver_protocol::SetResult> {
667 self.transport
668 .client()
669 .call(resolver_protocol::method::SET, params, deadline_unix_ms)
670 .await
671 }
672
673 pub async fn delete(
676 &self,
677 params: &resolver_protocol::DeleteParams,
678 deadline_unix_ms: u64,
679 ) -> Result<resolver_protocol::DeleteResult> {
680 self.transport
681 .client()
682 .call(resolver_protocol::method::DELETE, params, deadline_unix_ms)
683 .await
684 }
685
686 pub fn capabilities(&self) -> &HashSet<String> {
687 &self.capabilities
688 }
689
690 pub fn supports(&self, method: &str) -> bool {
693 self.capabilities.contains(method)
694 }
695
696 pub fn initialized(&self) -> &ResolverInitializedApplication {
697 &self.initialized
698 }
699
700 pub fn is_closed(&self) -> bool {
701 self.transport.client().is_closed()
702 }
703
704 pub async fn close(&self, deadline_unix_ms: u64) -> Result<()> {
705 self.transport.close(deadline_unix_ms).await
706 }
707}
708
709impl Drop for ChildSession {
710 fn drop(&mut self) {
711 self.monitor_cancel.cancel();
716 if let Ok(mut child) = self.child.try_lock() {
717 let _ = child.start_kill();
718 }
719 }
720}
721
722pub async fn spawn<A, B>(
723 options: LaunchOptions,
724 initialize: InitializeParams<A>,
725 startup_deadline_unix_ms: u64,
726) -> Result<(ChildSession, InitializeResult<B>)>
727where
728 A: Serialize,
729 B: DeserializeOwned,
730{
731 spawn_with_callbacks(options, initialize, startup_deadline_unix_ms, None).await
732}
733
734pub async fn spawn_with_callbacks<A, B>(
737 options: LaunchOptions,
738 initialize: InitializeParams<A>,
739 startup_deadline_unix_ms: u64,
740 callbacks: Option<Arc<dyn CallbackHandler>>,
741) -> Result<(ChildSession, InitializeResult<B>)>
742where
743 A: Serialize,
744 B: DeserializeOwned,
745{
746 options.validate()?;
747 let mut command = Command::new(&options.executable);
748 command
749 .args(&options.arguments)
750 .stdin(Stdio::piped())
751 .stdout(Stdio::piped())
752 .stderr(Stdio::piped())
753 .kill_on_drop(true);
757 match &options.environment {
758 Environment::Inherit(overrides) => {
759 command.envs(overrides);
760 }
761 Environment::Replace(environment) => {
762 command.env_clear().envs(environment);
763 }
764 }
765
766 let mut child = command.spawn()?;
767 let stdin = child
768 .stdin
769 .take()
770 .ok_or(Error::Protocol("child stdin was not piped"))?;
771 let stdout = child
772 .stdout
773 .take()
774 .ok_or(Error::Protocol("child stdout was not piped"))?;
775 let mut stderr = child
776 .stderr
777 .take()
778 .ok_or(Error::Protocol("child stderr was not piped"))?;
779
780 let stderr_limit = options.max_stderr_bytes;
781 let stderr_task = tokio::spawn(async move {
782 let mut retained = Zeroizing::new(Vec::with_capacity(stderr_limit));
784 let mut buffer = Zeroizing::new(vec![0_u8; 4096]);
785 loop {
786 let read: usize = stderr.read(&mut buffer).await.unwrap_or_default();
787 if read == 0 {
788 break;
789 }
790 let available = stderr_limit.saturating_sub(retained.len());
791 retained.extend_from_slice(&buffer[..read.min(available)]);
792 }
793 retained
794 });
795
796 let child = Arc::new(Mutex::new(child));
797 let connect = Client::connect_with_callbacks(
798 stdout,
799 stdin,
800 initialize,
801 startup_deadline_unix_ms,
802 callbacks,
803 )
804 .await;
805 let (client, initialized) = match connect {
806 Ok(value) => value,
807 Err(error) => {
808 let mut child = child.lock().await;
809 let _ = child.start_kill();
810 let _ = tokio::time::timeout(REAP_GRACE, child.wait()).await;
815 drop(child);
816 finish_stderr(stderr_task, Instant::now() + REAP_GRACE).await;
817 return Err(error);
818 }
819 };
820
821 let monitor_cancel = CancellationToken::new();
822 let monitor_child = child.clone();
823 let monitor_client = client.clone();
824 let monitor_stop = monitor_cancel.clone();
825 let monitor = tokio::spawn(async move {
826 loop {
827 tokio::select! {
828 _ = monitor_stop.cancelled() => break,
829 _ = tokio::time::sleep(Duration::from_millis(25)) => {
830 let exited = monitor_child
831 .lock()
832 .await
833 .try_wait()
834 .ok()
835 .flatten()
836 .is_some();
837 if exited {
838 monitor_client
839 .abandon_after_process_exit(REAP_GRACE)
840 .await;
841 break;
842 }
843 }
844 }
845 }
846 });
847
848 Ok((
849 ChildSession {
850 client,
851 child,
852 monitor_cancel,
853 monitor: Mutex::new(Some(monitor)),
854 stderr: Mutex::new(Some(stderr_task)),
855 closed: AtomicBool::new(false),
856 },
857 initialized,
858 ))
859}
860
861async fn wait_until(child: &Arc<Mutex<Child>>, deadline: Instant) -> Result<bool> {
862 loop {
863 if child.lock().await.try_wait()?.is_some() {
864 return Ok(true);
865 }
866 if Instant::now() >= deadline {
867 return Ok(false);
868 }
869 tokio::time::sleep(Duration::from_millis(10)).await;
870 }
871}
872
873async fn finish_stderr(mut task: JoinHandle<Zeroizing<Vec<u8>>>, deadline: Instant) {
874 if tokio::time::timeout_at(deadline, &mut task).await.is_err() {
875 task.abort();
876 let _ = task.await;
877 }
878}
879
880#[cfg(test)]
881mod tests {
882 use super::*;
883 use crate::protocol::InitializeResult;
884 use crate::server::{ApplicationHandler, RequestContext, RpcResult, ServerConfig, serve};
885 use async_trait::async_trait;
886 use serde_json::{Value, json};
887 use tokio::sync::Notify;
888
889 struct GatedShutdown {
890 entered: Arc<Notify>,
891 release: Arc<Notify>,
892 }
893
894 #[async_trait]
895 impl ApplicationHandler for GatedShutdown {
896 fn protocol(&self) -> &'static str {
897 RESOLVER_PROTOCOL
898 }
899
900 fn capabilities(&self) -> Vec<String> {
901 vec!["resolver.get".to_string()]
902 }
903
904 async fn initialize(
905 &self,
906 _context: &RequestContext,
907 _application: Value,
908 ) -> RpcResult<Value> {
909 Ok(json!({}))
910 }
911
912 async fn call(
913 &self,
914 _context: RequestContext,
915 _method: &str,
916 _params: Value,
917 ) -> RpcResult<Value> {
918 Ok(json!({}))
919 }
920
921 async fn shutdown(&self) {
922 self.entered.notify_one();
923 self.release.notified().await;
924 }
925 }
926
927 #[cfg(target_os = "linux")]
930 #[tokio::test]
931 async fn a_dropped_launch_kills_its_child() {
932 let directory = tempfile::tempdir().unwrap();
933 let pid_file = directory.path().join("pid");
934 let options = LaunchOptions {
935 executable: "/bin/sh".into(),
936 arguments: vec![
937 "-c".into(),
938 "echo $$ > \"$0\"; exec cat > /dev/null".into(),
940 pid_file.clone().into(),
941 ],
942 environment: Environment::Inherit(Default::default()),
943 allow_path_discovery: false,
944 max_stderr_bytes: 4096,
945 };
946 let initialize = InitializeParams {
947 protocol: RESOLVER_PROTOCOL.to_string(),
948 versions: vec![PROTOCOL_VERSION],
949 client: Product {
950 name: "drop-test".to_string(),
951 version: "1".to_string(),
952 },
953 limits: Limits {
954 max_frame_bytes: 32 * 1024,
955 max_in_flight: 4,
956 },
957 client_methods: Vec::new(),
958 application: json!({}),
959 };
960 let launch = spawn::<Value, Value>(
961 options,
962 initialize,
963 deadline_unix_ms_after(Duration::from_secs(300)),
964 );
965 let mut launch = Box::pin(launch);
967 let started = async {
968 loop {
969 if let Ok(pid) = std::fs::read_to_string(&pid_file)
970 && let Some(pid) = pid.strip_suffix('\n')
971 {
972 return pid.to_string();
973 }
974 tokio::task::yield_now().await;
975 }
976 };
977 let pid = tokio::select! {
978 biased;
979 _ = &mut launch => panic!("an endpoint that never answers cannot initialize"),
980 pid = started => pid,
981 };
982 drop(launch);
983
984 let stat = format!("/proc/{pid}/stat");
987 loop {
988 match std::fs::read_to_string(&stat) {
989 Err(_) => break,
990 Ok(stat)
991 if stat
992 .rsplit_once(')')
993 .unwrap()
994 .1
995 .trim_start()
996 .starts_with('Z') =>
997 {
998 break;
999 }
1000 Ok(_) => tokio::task::yield_now().await,
1001 }
1002 }
1003 }
1004
1005 #[tokio::test]
1006 async fn stderr_cleanup_is_bounded() {
1007 let task = tokio::spawn(async {
1008 std::future::pending::<()>().await;
1009 Zeroizing::new(Vec::new())
1010 });
1011 tokio::time::timeout(
1012 Duration::from_millis(250),
1013 finish_stderr(task, Instant::now() + Duration::from_millis(20)),
1014 )
1015 .await
1016 .expect("stderr cleanup exceeded its deadline");
1017 }
1018
1019 #[tokio::test]
1025 async fn process_exit_waits_for_a_buffered_shutdown_response() {
1026 for _ in 0..32 {
1027 let entered = Arc::new(Notify::new());
1028 let release = Arc::new(Notify::new());
1029 let handler = Arc::new(GatedShutdown {
1030 entered: entered.clone(),
1031 release: release.clone(),
1032 });
1033 let (client_io, server_io) = tokio::io::duplex(64 * 1024);
1034 let (client_read, client_write) = tokio::io::split(client_io);
1035 let (server_read, server_write) = tokio::io::split(server_io);
1036 let server = tokio::spawn(serve(
1037 server_read,
1038 server_write,
1039 handler,
1040 ServerConfig::default(),
1041 ));
1042 let initialize = InitializeParams {
1043 protocol: RESOLVER_PROTOCOL.to_string(),
1044 versions: vec![PROTOCOL_VERSION],
1045 client: Product {
1046 name: "exit-race-test".to_string(),
1047 version: "1".to_string(),
1048 },
1049 limits: Limits {
1050 max_frame_bytes: 32 * 1024,
1051 max_in_flight: 4,
1052 },
1053 client_methods: Vec::new(),
1054 application: json!({}),
1055 };
1056 let (client, _): (Client, InitializeResult<Value>) = Client::connect(
1057 client_read,
1058 client_write,
1059 initialize,
1060 deadline_unix_ms_after(Duration::from_secs(2)),
1061 )
1062 .await
1063 .unwrap();
1064
1065 let close_client = client.clone();
1066 let close = tokio::spawn(async move {
1067 close_client
1068 .close(deadline_unix_ms_after(Duration::from_secs(2)))
1069 .await
1070 });
1071 entered.notified().await;
1072 let monitor_client = client.clone();
1073 let monitor = tokio::spawn(async move {
1074 monitor_client
1075 .abandon_after_process_exit(Duration::from_secs(1))
1076 .await;
1077 });
1078 release.notify_one();
1079
1080 close.await.unwrap().unwrap();
1081 monitor.await.unwrap();
1082 server.await.unwrap().unwrap();
1083 }
1084 }
1085}