Skip to main content

liminal_cli/mcp/
serve.rs

1//! Blocking stdin and blocking tap readers share a single framed writer.
2
3use super::framing::{self, FrameError, INVALID_REQUEST, Incoming, PARSE_ERROR};
4use super::notifications::{self, Batch};
5use super::server::Server;
6use super::subscriptions::{Pending, Service};
7use super::writer::Writer;
8use crate::network::Rail;
9use serde_json::Value;
10use std::io::{self, BufRead, Write};
11use std::sync::Arc;
12use std::sync::atomic::Ordering;
13use std::thread::ScopedJoinHandle;
14
15type ReaderThread<'scope> = (Arc<Service>, ScopedJoinHandle<'scope, io::Result<()>>);
16
17impl<R: Rail> Server<R> {
18    /// Serves requests while tap readers send unsolicited logging lines.
19    /// EOF signals and joins every receiver before this returns.
20    ///
21    /// # Errors
22    /// Returns the input, output, framing or subscription-teardown failure.
23    pub fn serve<I: BufRead, O: Write + Send>(
24        &mut self,
25        input: &mut I,
26        output: &mut O,
27    ) -> io::Result<()> {
28        if let Err(error) = self.start_listens() {
29            return combine(
30                Err(error),
31                self.channel_source
32                    .as_ref()
33                    .map_or(Ok(()), |source| source.revoke().map_err(io::Error::other)),
34            );
35        }
36        let writer = Arc::new(Writer::new(
37            output,
38            self.frame_bytes,
39            super::writer::Answers {
40                bytes: self.answer_bytes,
41                dir: self.answer_dir.clone(),
42            },
43            Arc::clone(&self.warning_only),
44            Arc::clone(&self.subscriptions),
45            Arc::clone(&self.listens),
46        ));
47        let served = std::thread::scope(|scope| {
48            let mut readers = Vec::new();
49            let mut channels = Vec::new();
50            let served = self.requests(scope, input, &writer, &mut readers, &mut channels);
51            let mut result = combine(
52                served,
53                self.subscriptions.close_all().map_err(io::Error::other),
54            );
55            result = combine(result, self.listens.close_all().map_err(io::Error::other));
56            for (_, reader) in readers {
57                result = combine(result, joined(reader));
58            }
59            for (_, reader) in channels {
60                result = combine(result, super::channel_serve::joined(reader, &self.listens));
61            }
62            combine(result, writer.check())
63        });
64        combine(
65            served,
66            self.channel_source
67                .as_ref()
68                .map_or(Ok(()), |source| source.revoke().map_err(io::Error::other)),
69        )
70    }
71
72    fn requests<'scope, 'env, I: BufRead, O: Write + Send + 'scope>(
73        &mut self,
74        scope: &'scope std::thread::Scope<'scope, 'env>,
75        input: &mut I,
76        writer: &Arc<Writer<O>>,
77        readers: &mut Vec<ReaderThread<'scope>>,
78        channels: &mut Vec<super::channel_serve::Reader<'scope>>,
79    ) -> io::Result<()> {
80        let context = super::channel_serve::Context {
81            config: self.listen_config.clone(),
82            device: self.rail().server_address().to_owned(),
83            record_bytes: self
84                .channel_source
85                .as_ref()
86                .map_or(self.frame_bytes, |source| source.record_bytes),
87        };
88        super::channel_serve::spawn(scope, channels, &self.listens, writer, &context)?;
89        loop {
90            writer.check()?;
91            join_closed(readers)?;
92            super::channel_serve::join_closed(channels, &self.listens)?;
93            let incoming = framing::read_message(input, self.frame_bytes);
94            writer.check()?;
95            let answer = match incoming {
96                Ok(None) => return Ok(()),
97                Ok(Some(Incoming::Notification { .. })) => continue,
98                Ok(Some(Incoming::Request { id, method, params })) => {
99                    self.answer(&id, &method, &params)
100                }
101                Err(FrameError::Stream { message }) => return Err(io::Error::other(message)),
102                Err(error @ (FrameError::Overlong { .. } | FrameError::NotRequest { .. })) => {
103                    framing::error(&Value::Null, INVALID_REQUEST, &error.to_string(), None)
104                }
105                Err(error @ FrameError::NotJson { .. }) => {
106                    framing::error(&Value::Null, PARSE_ERROR, &error.to_string(), None)
107                }
108            };
109            join_closed(readers)?;
110            super::channel_serve::join_closed(channels, &self.listens)?;
111            writer.write(answer, None, false)?;
112            spawn_taps(
113                scope,
114                self.subscriptions.pending().map_err(io::Error::other)?,
115                writer,
116                readers,
117            )?;
118            super::channel_serve::spawn(scope, channels, &self.listens, writer, &context)?;
119        }
120    }
121}
122
123fn joined(reader: ScopedJoinHandle<'_, io::Result<()>>) -> io::Result<()> {
124    reader
125        .join()
126        .map_err(|_| io::Error::other("a tap receiver thread panicked"))?
127}
128
129fn spawn_taps<'scope, 'env, O: Write + Send + 'scope>(
130    scope: &'scope std::thread::Scope<'scope, 'env>,
131    pending: Vec<Pending>,
132    writer: &Arc<Writer<O>>,
133    readers: &mut Vec<ReaderThread<'scope>>,
134) -> io::Result<()> {
135    for pending in pending {
136        let writer = Arc::clone(writer);
137        let service = Arc::clone(&pending.service);
138        let reader = std::thread::Builder::new()
139            .name("liminal-mcp-tap".to_owned())
140            .spawn_scoped(scope, move || receive(&pending, &writer))?;
141        readers.push((service, reader));
142    }
143    Ok(())
144}
145
146fn join_closed(readers: &mut Vec<ReaderThread<'_>>) -> io::Result<()> {
147    let mut n = 0;
148    while n < readers.len() {
149        if readers[n].0.active.load(Ordering::SeqCst) {
150            n += 1;
151        } else {
152            joined(readers.swap_remove(n).1)?;
153        }
154    }
155    Ok(())
156}
157
158fn combine(first: io::Result<()>, second: io::Result<()>) -> io::Result<()> {
159    match (first, second) {
160        (Ok(()), result) | (result, Ok(())) => result,
161        (Err(first), Err(second)) => Err(io::Error::other(format!("{first}; {second}"))),
162    }
163}
164
165fn receive<O: Write>(pending: &Pending, writer: &Writer<O>) -> io::Result<()> {
166    while pending.service.active.load(Ordering::SeqCst) {
167        let batch = pending
168            .reader
169            .receive()
170            .map_err(|error| error.to_string())
171            .and_then(|record| Batch::read(record, &pending.subject));
172        let batch = match batch {
173            Ok(batch) => batch,
174            Err(reason) => {
175                if pending.service.active.swap(false, Ordering::SeqCst) {
176                    let closed = pending.service.close();
177                    let reason = match closed {
178                        Ok(()) => reason,
179                        Err(words) => format!("{reason}; subscription teardown failed: {words}"),
180                    };
181                    return writer.write(
182                        notifications::warning(
183                            &pending.service,
184                            &pending.subject,
185                            "ended",
186                            &format!("subscription to {} ended: {reason}", pending.service.name),
187                        ),
188                        Some(&pending.service),
189                        true,
190                    );
191                }
192                return Ok(());
193            }
194        };
195        writer.record(&pending.service, &pending.subject, &batch)?;
196    }
197    Ok(())
198}