async-lsp 0.2.4

Asynchronous Language Server Protocol (LSP) framework based on tower
Documentation
#![cfg_attr(not(unix), allow(unused))]
use std::io::{Read, Write};
use std::process::Stdio;
use std::time::Duration;

use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::time::timeout;

const CHILD_ENV: &str = "IS_STDIO_TEST_CHILD";

const READ_TIMEOUT: Duration = Duration::from_millis(500);
const SCHED_TIMEOUT: Duration = Duration::from_millis(100);

// Do nothing for non-UNIX targets: `stdio` module is not available.
#[cfg(not(unix))]
fn main() {}

#[cfg(unix)]
fn main() {
    if std::env::var(CHILD_ENV).is_err() {
        parent();
    } else {
        tokio::runtime::Builder::new_current_thread()
            .enable_all()
            .build()
            .unwrap()
            .block_on(child());
    }
}

#[cfg(unix)]
fn parent() {
    let this_exe = std::env::current_exe().unwrap();
    let mut child = std::process::Command::new(this_exe)
        .env(CHILD_ENV, "1")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::inherit())
        .spawn()
        .expect("failed to spawn");
    let childin = child.stdin.as_mut().unwrap();
    let childout = child.stdout.as_mut().unwrap();

    let mut buf = [0u8; 64];
    // Wait for the signal.
    assert_eq!(childout.read(&mut buf).unwrap(), 4);
    assert_eq!(&buf[..4], b"ping");
    // Reply back.
    childin.write_all(b"pong").unwrap();

    // NB. Wait for its exit first, without draining `childout`. Because the child keeps writing
    // until the kernel buffer is full.
    let output = child.wait_with_output().unwrap();
    assert!(output.status.success());
    // The last one is written by the std blocking call `print!`.
    assert_eq!(output.stdout, b"2");
}

#[cfg(unix)]
async fn child() {
    use async_lsp::stdio::{PipeStdin, PipeStdout};

    let mut stdin = PipeStdin::lock_tokio().unwrap();
    let mut stdout = PipeStdout::lock_tokio().unwrap();
    let mut buf = [0u8; 64];

    // Should be blocked since we are holding lock guards in `PipeStd{in,out}`.
    let std_stdin = tokio::task::spawn_blocking(|| drop(std::io::stdin().lock()));
    let std_stdout = tokio::task::spawn_blocking(|| print!("2"));

    timeout(READ_TIMEOUT, stdin.read(&mut buf))
        .await
        .expect_err("should timeout");

    // Signal the parent to send us something. This should not block due to pipe buffer.
    timeout(Duration::ZERO, stdout.write_all(b"ping"))
        .await
        .expect("should not block")
        .unwrap();

    assert_eq!(
        timeout(SCHED_TIMEOUT, stdin.read(&mut buf))
            .await
            .expect("should not timeout")
            .expect("should read something"),
        4
    );
    assert_eq!(&buf[..4], b"pong");

    // Still blocked yet.
    assert!(!std_stdin.is_finished());
    assert!(!std_stdout.is_finished());

    // Drop lock guards, then std operations unblock.
    drop(stdin);
    drop(stdout);
    timeout(SCHED_TIMEOUT, std_stdin)
        .await
        .expect("no timeout")
        .expect("no panic");
    timeout(SCHED_TIMEOUT, std_stdout)
        .await
        .expect("no timeout")
        .expect("no panic");
}