eska_tool_runtime/
exchange.rs1use crate::{
4 Component, Error,
5 process::{OwnedChild, SignalState},
6 query::{bounded, decode},
7};
8use serde_json::Value;
9#[cfg(unix)]
10use std::io;
11use std::{
12 ffi::OsString,
13 path::Path,
14 process::{Command, Stdio},
15 sync::{Arc, atomic::Ordering, mpsc},
16 thread,
17 time::{Duration, Instant},
18};
19
20#[derive(Clone)]
22pub struct Operation(Arc<SignalState>);
23
24impl std::fmt::Debug for Operation {
25 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 formatter
28 .debug_struct("Operation")
29 .field("cancelled", &self.is_cancelled())
30 .finish_non_exhaustive()
31 }
32}
33
34impl Operation {
35 pub fn new() -> Result<Self, Error> {
40 SignalState::new()
41 .map(|state| Self(Arc::new(state)))
42 .map_err(Error::Signal)
43 }
44
45 pub(crate) fn received_signal(&self) -> Option<i32> {
47 i32::try_from(self.0.received.load(Ordering::Acquire))
48 .ok()
49 .filter(|value| *value != 0)
50 }
51
52 #[must_use]
54 pub fn is_cancelled(&self) -> bool {
55 self.0.received.load(Ordering::Acquire) != 0
56 }
57}
58
59pub fn exchange(
67 component: Component,
68 binary: &Path,
69 args: &[OsString],
70 operation: &str,
71 scope: &Operation,
72) -> Result<Value, Error> {
73 if scope.is_cancelled() {
74 return Err(Error::Cancelled);
75 }
76 let child = Command::new(binary)
77 .args(args)
78 .stdin(Stdio::inherit())
79 .stdout(Stdio::piped())
80 .stderr(Stdio::inherit())
81 .spawn()
82 .map_err(|source| Error::Process { component, source })?;
83 let mut child = OwnedChild(child);
84 let stdout = child.0.stdout.take().ok_or(Error::InvalidResponse)?;
85 let (sender, receiver) = mpsc::channel();
86 thread::Builder::new()
87 .name("eska-component-reply".into())
88 .spawn(move || {
89 let _ = sender.send(bounded(component, stdout));
90 })
91 .map_err(|source| Error::Process { component, source })?;
92 let mut output = None;
93 let mut status = None;
94 let mut cancellation = None;
95 loop {
96 match receiver.try_recv() {
97 Ok(result) => output = Some(result?),
98 Err(mpsc::TryRecvError::Disconnected) if output.is_none() => {
99 return Err(Error::InvalidResponse);
100 }
101 Err(_) => {}
102 }
103 if status.is_none() {
104 status = child
105 .0
106 .try_wait()
107 .map_err(|source| Error::Process { component, source })?;
108 }
109 if let Some(status) = status
110 && let Some(output) = &output
111 {
112 return if scope.is_cancelled() {
113 Err(Error::Cancelled)
114 } else {
115 decode(component, status.success(), output, operation)
116 };
117 }
118 if scope.is_cancelled() && cancellation.is_none() {
119 #[cfg(unix)]
121 forward_cancel(&child, scope).map_err(Error::Signal)?;
122 cancellation = Some(Instant::now());
123 }
124 if cancellation.is_some_and(|started| started.elapsed() >= Duration::from_secs(30)) {
125 return Err(Error::Cancelled);
126 }
127 thread::sleep(Duration::from_millis(10));
128 }
129}
130
131#[cfg(unix)]
133fn forward_cancel(child: &OwnedChild, scope: &Operation) -> io::Result<()> {
134 use nix::{
135 errno::Errno,
136 sys::signal::{Signal, kill},
137 unistd::Pid,
138 };
139 let pid = i32::try_from(child.0.id()).map_err(io::Error::other)?;
140 let signal =
141 i32::try_from(scope.0.received.load(Ordering::Acquire)).map_err(io::Error::other)?;
142 match kill(
143 Pid::from_raw(pid),
144 Signal::try_from(signal).map_err(io::Error::other)?,
145 ) {
146 Ok(()) | Err(Errno::ESRCH) => Ok(()),
147 Err(error) => Err(io::Error::from(error)),
148 }
149}