blob: 6b5955eb83e5f627fc3622463281d4b579ebda61 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use std::{
io,
pin::Pin,
task::{Context, Poll, ready},
};
use tokio::{
io::{AsyncRead, AsyncWrite, ReadBuf},
net::windows::named_pipe::{NamedPipeClient, NamedPipeServer},
};
/// Both directions share one actual handle. Windows pipes have no write-half
/// EOF: shutdown closes that handle and readers observe EOF, never a fake Ack.
pub struct LocalStream {
pipe: Option<Pipe>,
}
enum Pipe {
Server(NamedPipeServer),
Client(NamedPipeClient),
}
impl LocalStream {
pub(super) fn server(pipe: NamedPipeServer) -> Self {
Self {
pipe: Some(Pipe::Server(pipe)),
}
}
pub(super) fn client(pipe: NamedPipeClient) -> Self {
Self {
pipe: Some(Pipe::Client(pipe)),
}
}
}
impl AsyncRead for LocalStream {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
match self.pipe.as_mut() {
Some(Pipe::Server(pipe)) => Pin::new(pipe).poll_read(cx, buf),
Some(Pipe::Client(pipe)) => Pin::new(pipe).poll_read(cx, buf),
None => Poll::Ready(Ok(())),
}
}
}
impl AsyncWrite for LocalStream {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
match self.pipe.as_mut() {
Some(Pipe::Server(pipe)) => Pin::new(pipe).poll_write(cx, buf),
Some(Pipe::Client(pipe)) => Pin::new(pipe).poll_write(cx, buf),
None => Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())),
}
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
// Tokio flush is a no-op. A zero-byte write observes Mio's actual
// previous-write completion/error and clears stale readiness on WouldBlock.
loop {
let result = match self.pipe.as_ref() {
Some(Pipe::Server(pipe)) => {
ready!(pipe.poll_write_ready(cx))?;
pipe.try_write(&[])
}
Some(Pipe::Client(pipe)) => {
ready!(pipe.poll_write_ready(cx))?;
pipe.try_write(&[])
}
None => return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())),
};
match result {
Ok(_) => return Poll::Ready(Ok(())),
Err(error) if error.kind() == io::ErrorKind::WouldBlock => continue,
Err(error) => return Poll::Ready(Err(error)),
}
}
}
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
if self.pipe.is_none() {
return Poll::Ready(Ok(()));
}
// Complete prior writes before releasing the real handle. Even a flush
// error closes it; EOF is cancellation, never a remote cleanup Ack.
let result = ready!(self.as_mut().poll_flush(cx));
drop(self.pipe.take());
Poll::Ready(result)
}
}
#[cfg(test)]
mod tests {
use crate::ipc::LocalListener;
use std::{path::PathBuf, time::Duration};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::windows::named_pipe::ClientOptions,
};
#[tokio::test]
async fn flush_waits_for_pending_bytes_and_reports_a_disconnected_reader() {
let path = PathBuf::from(format!(
r"\\.\pipe\maka-flush-test-{}",
uuid::Uuid::new_v4()
));
let mut listener = LocalListener::bind(&path).unwrap();
let payload = vec![b'x'; 512 * 1024];
let mut client = ClientOptions::new().open(&path).unwrap();
let mut stream = listener.accept().await.unwrap();
stream.write_all(&payload).await.unwrap();
assert!(
tokio::time::timeout(Duration::from_millis(50), stream.flush())
.await
.is_err()
);
let read = tokio::spawn(async move {
let mut actual = vec![0; 512 * 1024];
client.read_exact(&mut actual).await.unwrap();
(client, actual)
});
tokio::time::timeout(Duration::from_secs(5), stream.flush())
.await
.unwrap()
.unwrap();
let (mut client, actual) = tokio::time::timeout(Duration::from_secs(5), read)
.await
.unwrap()
.unwrap();
assert_eq!(actual, payload);
stream.write_all(b"next").await.unwrap();
stream.flush().await.unwrap();
let mut next = [0; 4];
tokio::time::timeout(Duration::from_secs(5), client.read_exact(&mut next))
.await
.unwrap()
.unwrap();
assert_eq!(
&next, b"next",
"flush must not deliver an empty message or EOF"
);
drop(client);
drop(stream);
let client = ClientOptions::new().open(&path).unwrap();
let mut stream = listener.accept().await.unwrap();
stream.write_all(&payload).await.unwrap();
drop(client);
assert!(
tokio::time::timeout(Duration::from_secs(5), stream.flush())
.await
.unwrap()
.is_err()
);
}
}