liminal_server/server/connection/link_route/
executor.rs1use crate::link::LinkHost;
4use crate::link::calls::{CallError, CallExecution, CallExecutor, CallReply, CallRequest};
5use crate::server::connection::{
6 ConnectionHandle, ConnectionSupervisor, LoopbackClientEnd, LoopbackDuplex,
7};
8use liminal::protocol::{Frame, FrameHeader, codec};
9use std::io::{Read, Write};
10use std::num::NonZeroUsize;
11use std::sync::atomic::{AtomicUsize, Ordering};
12use std::sync::{Arc, Condvar, Mutex};
13use std::thread::JoinHandle;
14use zeroize::Zeroizing;
15
16struct Executor {
17 supervisor: ConnectionSupervisor,
18 ring_bytes: NonZeroUsize,
19 answer_bytes: NonZeroUsize,
20 retained: Arc<Retirement>,
21}
22#[derive(Default)]
23struct Retirement {
24 count: AtomicUsize,
25 signal: Mutex<()>,
26 changed: Condvar,
27}
28impl std::fmt::Debug for Executor {
29 fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 out.debug_struct("LinkCallExecutor")
31 .field("ring_bytes", &self.ring_bytes)
32 .field("answer_bytes", &self.answer_bytes)
33 .finish_non_exhaustive()
34 }
35}
36
37impl ConnectionSupervisor {
38 pub fn install_link_call_executor(
45 &self,
46 host: &LinkHost,
47 ring_bytes: NonZeroUsize,
48 answer_bytes: NonZeroUsize,
49 ) -> Result<Arc<dyn CallExecutor>, CallError> {
50 let executor: Arc<dyn CallExecutor> = Arc::new(Executor {
51 supervisor: self.clone(),
52 ring_bytes,
53 answer_bytes,
54 retained: Arc::new(Retirement::default()),
55 });
56 host.install_call_executor(&executor)?;
57 Ok(executor)
58 }
59}
60
61impl CallExecutor for Executor {
62 fn retained(&self) -> usize {
63 self.retained.count.load(Ordering::Acquire)
64 }
65 fn observe_empty(&self) -> Result<(), CallError> {
66 let signal = self
67 .retained
68 .signal
69 .lock()
70 .map_err(|_| CallError::Unavailable)?;
71 let signal = self
72 .retained
73 .changed
74 .wait_while(signal, |()| {
75 self.retained.count.load(Ordering::Acquire) != 0
76 })
77 .map_err(|_| CallError::Unavailable)?;
78 drop(signal);
79 Ok(())
80 }
81 fn start(
82 &self,
83 request: CallRequest,
84 completed: Box<dyn FnOnce(Result<CallReply, CallError>) + Send>,
85 ) -> Result<Box<dyn CallExecution>, CallError> {
86 let correlation = request.correlation()?;
87 let signal = self
88 .retained
89 .signal
90 .lock()
91 .map_err(|_| CallError::Unavailable)?;
92 let (mut client, server) = LoopbackDuplex::bounded(self.ring_bytes.get());
93 let handle = self
94 .supervisor
95 .spawn_loopback_connection(server)
96 .map_err(|_| CallError::Capacity)?;
97 let id = request.id();
98 let bound = self.answer_bytes.get();
99 let dispatcher = tracing::dispatcher::get_default(Clone::clone);
100 let worker = std::thread::Builder::new()
101 .name("liminal-linked-call".into())
102 .spawn(move || {
103 tracing::dispatcher::with_default(&dispatcher, || {
104 let answer = exchange(&mut client, &request, correlation, bound)
105 .and_then(|frame| CallReply::new(id, frame));
106 drop(request);
107 drop(client);
108 completed(answer);
109 });
110 })
111 .map_err(|_| CallError::Unavailable)?;
112 self.retained.count.fetch_add(1, Ordering::AcqRel);
113 drop(signal);
114 Ok(Box::new(Execution {
115 handle,
116 worker: Some(worker),
117 retained: Arc::clone(&self.retained),
118 }))
119 }
120}
121
122struct Execution {
123 handle: ConnectionHandle,
124 worker: Option<JoinHandle<()>>,
125 retained: Arc<Retirement>,
126}
127impl std::fmt::Debug for Execution {
128 fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 out.debug_struct("LinkCallExecution")
130 .field("connection", &self.handle.pid())
131 .finish_non_exhaustive()
132 }
133}
134impl CallExecution for Execution {
135 fn finish(&mut self) -> Result<(), CallError> {
136 if self.handle.is_live() && self.handle.request_crash().is_err() {
143 tracing::debug!(
144 connection = self.handle.pid(),
145 "linked call connection is already exiting"
146 );
147 }
148 if let Some(worker) = self.worker.take() {
149 worker.join().map_err(|_| CallError::Unavailable)?;
150 }
151 Ok(())
152 }
153}
154impl Drop for Execution {
155 fn drop(&mut self) {
156 if let Err(error) = self.finish() {
157 tracing::error!(%error, "linked call execution retirement failed");
158 }
159 if let Ok(signal) = self.retained.signal.lock() {
160 self.retained.count.fetch_sub(1, Ordering::AcqRel);
161 self.retained.changed.notify_all();
162 drop(signal);
163 } else {
164 tracing::error!("link_call_retirement_signal_unavailable");
165 }
166 }
167}
168
169fn exchange(
170 client: &mut LoopbackClientEnd,
171 request: &CallRequest,
172 correlation: (u32, u64),
173 bound: usize,
174) -> Result<Frame, CallError> {
175 client
176 .write_all(request.wire_bytes())
177 .map_err(|_| CallError::Unavailable)?;
178 match read(client, bound)? {
179 Frame::ConnectAck { .. } => {}
180 Frame::ConnectError {
181 reason_code,
182 message,
183 ..
184 } => return Ok(refusal(correlation, reason_code, message)),
185 _ => return Err(CallError::Invalid),
186 }
187 let frame = read(client, bound)?;
188 match &frame {
189 Frame::ConversationMessage {
190 stream_id,
191 conversation_id,
192 ..
193 }
194 | Frame::ConversationError {
195 stream_id,
196 conversation_id,
197 ..
198 } if (*stream_id, *conversation_id) == correlation => Ok(frame),
199 _ => Err(CallError::Correlation),
200 }
201}
202
203fn read(client: &mut impl Read, bound: usize) -> Result<Frame, CallError> {
204 let mut header = [0; FrameHeader::WIRE_LEN];
205 client
206 .read_exact(&mut header)
207 .map_err(|_| CallError::PeerGone)?;
208 let length = usize::try_from(u32::from_be_bytes(
209 header[6..10].try_into().map_err(|_| CallError::Invalid)?,
210 ))
211 .map_err(|_| CallError::Bound)?;
212 let total = header
213 .len()
214 .checked_add(length)
215 .filter(|length| *length <= bound)
216 .ok_or(CallError::Bound)?;
217 let mut bytes = Zeroizing::new(vec![0; total]);
218 bytes[..header.len()].copy_from_slice(&header);
219 client
220 .read_exact(&mut bytes[header.len()..])
221 .map_err(|_| CallError::PeerGone)?;
222 let (frame, used) = codec::decode(&bytes).map_err(|_| CallError::Invalid)?;
223 if used != total {
224 return Err(CallError::Invalid);
225 }
226 Ok(frame)
227}
228
229const fn refusal(correlation: (u32, u64), reason_code: u16, message: Option<String>) -> Frame {
230 Frame::ConversationError {
231 flags: 0,
232 stream_id: correlation.0,
233 conversation_id: correlation.1,
234 reason_code,
235 message,
236 }
237}