test(xy): logs --tail and --follow

Fix a deadlock in the log-stream handler that caused all logs
requests to hang: Connection used a single Mutex<JsonFramed> for
both reads and writes, so the serve loop holding the read lock
blocked the spawned notification task from writing.  Split
Connection into separate reader and writer mutexes.

Also fix a response/notification ordering race: the log task now
waits for an explicit ready signal sent by serve after writing the
LOGS response, ensuring notifications never arrive at the client
before their initiating response.
This commit is contained in:
2026-05-25 12:17:32 +02:00
parent 15791c628b
commit b1e7dea739
7 changed files with 191 additions and 43 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
use crate::envelope::{Incoming, Notification, Request};
use crate::framing::JsonFramed;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde::de::DeserializeOwned;
use std::path::Path;
use thiserror::Error;
use tokio::net::UnixStream;
+49 -7
View File
@@ -16,9 +16,7 @@ impl JsonFramed {
}
}
pub async fn read<T: serde::de::DeserializeOwned>(
&mut self,
) -> std::io::Result<Option<T>> {
pub async fn read<T: serde::de::DeserializeOwned>(&mut self) -> std::io::Result<Option<T>> {
let mut buf = String::new();
let n = self.reader.read_line(&mut buf).await?;
if n == 0 {
@@ -38,6 +36,47 @@ impl JsonFramed {
}
}
pub struct JsonFramedReader {
inner: BufReader<tokio::net::unix::OwnedReadHalf>,
}
impl JsonFramedReader {
pub async fn read<T: serde::de::DeserializeOwned>(&mut self) -> std::io::Result<Option<T>> {
let mut buf = String::new();
let n = self.inner.read_line(&mut buf).await?;
if n == 0 {
return Ok(None);
}
let v: T = serde_json::from_str(buf.trim_end())
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
Ok(Some(v))
}
}
pub struct JsonFramedWriter {
inner: tokio::net::unix::OwnedWriteHalf,
}
impl JsonFramedWriter {
pub async fn write<T: Serialize>(&mut self, value: &T) -> std::io::Result<()> {
let mut bytes = serde_json::to_vec(value)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
bytes.push(b'\n');
self.inner.write_all(&bytes).await?;
self.inner.flush().await
}
}
pub fn split(stream: UnixStream) -> (JsonFramedReader, JsonFramedWriter) {
let (r, w) = stream.into_split();
(
JsonFramedReader {
inner: BufReader::new(r),
},
JsonFramedWriter { inner: w },
)
}
#[cfg(test)]
mod tests {
use super::*;
@@ -61,10 +100,13 @@ mod tests {
.await
.unwrap();
let got: Option<M> = sb.read().await.unwrap();
assert_eq!(got, Some(M {
x: 1,
name: "hi".into()
}));
assert_eq!(
got,
Some(M {
x: 1,
name: "hi".into()
})
);
}
#[tokio::test]
+3 -3
View File
@@ -7,8 +7,8 @@ pub mod server;
pub use client::{Client, ClientError};
pub use envelope::{
err_response, notification, ok_response, request, Incoming, Notification, Request, Response,
RpcError,
Incoming, Notification, Request, Response, RpcError, err_response, notification, ok_response,
request,
};
pub use framing::JsonFramed;
pub use server::{bind, Connection};
pub use server::{Connection, bind};
+9 -6
View File
@@ -1,33 +1,36 @@
use crate::envelope::{Incoming, Notification, Response};
use crate::framing::JsonFramed;
use crate::framing::{JsonFramedReader, JsonFramedWriter};
use std::path::Path;
use std::sync::Arc;
use tokio::net::{UnixListener, UnixStream};
use tokio::sync::Mutex;
pub struct Connection {
inner: Arc<Mutex<JsonFramed>>,
reader: Mutex<JsonFramedReader>,
writer: Arc<Mutex<JsonFramedWriter>>,
}
impl Connection {
pub fn new(stream: UnixStream) -> Self {
let (reader, writer) = crate::framing::split(stream);
Self {
inner: Arc::new(Mutex::new(JsonFramed::new(stream))),
reader: Mutex::new(reader),
writer: Arc::new(Mutex::new(writer)),
}
}
pub async fn read_incoming(&self) -> std::io::Result<Option<Incoming>> {
let mut g = self.inner.lock().await;
let mut g = self.reader.lock().await;
g.read::<Incoming>().await
}
pub async fn write_response(&self, r: &Response) -> std::io::Result<()> {
let mut g = self.inner.lock().await;
let mut g = self.writer.lock().await;
g.write(r).await
}
pub async fn write_notification(&self, n: &Notification) -> std::io::Result<()> {
let mut g = self.inner.lock().await;
let mut g = self.writer.lock().await;
g.write(n).await
}
}