为何将子进程stdin存入变量后写入操作失效?
Dart语言服务器通信异常:两段逻辑一致的Rust代码为何表现不同?
我在尝试与Dart语言服务器进程通信时遇到了诡异问题——两段逻辑上完全一致的Rust实现,一个能让服务器收到消息并返回错误(说明输入已被接收),另一个却完全无法将消息传递给服务器,求解释背后的原因。
可行的实现代码
use std::{process::Stdio, time::Duration}; use tokio::{io::AsyncWriteExt, process::Command, time::sleep}; #[tokio::main] async fn main() { let mut process = Command::new("dart") .args([ "language-server", "--protocol=analyzer", "--protocol-traffic-log=logs.txt", ]) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .kill_on_drop(true) .spawn() .expect("failed to spawn process"); process .stdin .take() .unwrap() .write_all(b"test") .await .unwrap(); sleep(Duration::from_secs(5)).await; }
对应的日志(log.txt)
注:输入格式无效,但关键是服务器明确收到了消息
1673866099379:Ver:1645600465579221612245:unknown.client.classic:unknown:1.33.1:2.18.0 1673866099410:Noti:{"event"::"server.connected","params"::{"version"::"1.33.1","pid"::16810}} 1673866099421:Req:test 1673866099422:Res:{"id"::"","error"::{"code"::"INVALID_REQUEST","message"::"Invalid request"}}
不可行的实现代码
唯一区别是将process.stdin.take().unwrap()的结果存入了变量:
use std::{process::Stdio, time::Duration}; use tokio::{io::AsyncWriteExt, process::Command, time::sleep}; #[tokio::main] async fn main() { let mut process = Command::new("dart") .args([ "language-server", "--protocol=analyzer", "--protocol-traffic-log=logs.txt", ]) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .kill_on_drop(true) .spawn() .expect("failed to spawn process"); let mut stdin = process.stdin.take().unwrap(); stdin.write_all(b"Test").await.unwrap(); sleep(Duration::from_secs(5)).await; }
对应的日志(log.txt)
1673866343914:Ver:1645600465579221612245:unknown.client.classic:unknown:1.33.1:2.18.0 1673866343942:Noti:{"event"::"server.connected","params"::{"version"::"1.33.1","pid"::16966}}
问题原因
核心在于Tokio异步Stdin句柄的缓冲区刷新时机:
- 第一个实现中,
write_all调用后,临时创建的Stdin句柄会立即被丢弃。当句柄被Drop时,Tokio会自动触发缓冲区刷新,确保数据被写入到Dart服务器的标准输入流中。 - 第二个实现中,Stdin句柄被存入变量
stdin,该变量会存活到函数末尾(sleep结束后)。在这段时间里,句柄不会被销毁,Tokio不会主动刷新缓冲区——异步IO默认采用缓冲机制,不会在每次write_all后立即写入底层流。因此Dart服务器根本没收到任何输入,自然不会生成请求日志。
解决方法
只需在write_all后手动调用flush()强制刷新缓冲区即可:
stdin.write_all(b"Test").await.unwrap(); stdin.flush().await.unwrap(); // 新增手动刷新逻辑
或者也可以通过让句柄提前被丢弃的方式触发自动刷新,比如将写入逻辑放在一个代码块中:
{ let mut stdin = process.stdin.take().unwrap(); stdin.write_all(b"Test").await.unwrap(); } // 代码块结束后stdin被Drop,自动刷新缓冲区 sleep(Duration::from_secs(5)).await;
内容的提问来源于stack exchange,提问作者Axot
相关产品推荐
相关产品推荐

