继续收敛插件 stderr 错误
保留有界脱敏的插件进程 stderr 退出状态同时显示退出码和插件正文
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fs;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::process::{Child, ChildStdin, Command, Stdio};
|
||||
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
||||
@@ -43,6 +43,7 @@ const RPC_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
const EDITOR_RPC_TIMEOUT: Duration = Duration::from_secs(90);
|
||||
const MAX_MANIFEST_BYTES: u64 = 1024 * 1024;
|
||||
const MAX_RPC_BYTES: usize = 2 * 1024 * 1024;
|
||||
const MAX_PLUGIN_STDERR_BYTES: usize = 64 * 1024;
|
||||
|
||||
const KNOWN_PERMISSIONS: &[&str] = &[
|
||||
"events.subscribe",
|
||||
@@ -163,6 +164,7 @@ struct RunningPlugin {
|
||||
lines: Option<Receiver<Result<String, String>>>,
|
||||
pending: PendingRpc,
|
||||
registrations: Arc<Mutex<PluginRegistrations>>,
|
||||
stderr_summary: Arc<Mutex<String>>,
|
||||
next_request_id: u64,
|
||||
editor_context: Option<ProjectContext>,
|
||||
active: Arc<AtomicBool>,
|
||||
@@ -578,7 +580,7 @@ fn spawn_plugin(manifest: &PluginManifest, root: &Path) -> Result<RunningPlugin,
|
||||
.env("AGC_PLUGIN_API_VERSION", &manifest.api_version)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::null());
|
||||
.stderr(Stdio::piped());
|
||||
for variable in [
|
||||
"PATH",
|
||||
"SystemRoot",
|
||||
@@ -606,6 +608,35 @@ fn spawn_plugin(manifest: &PluginManifest, root: &Path) -> Result<RunningPlugin,
|
||||
let mut child = command
|
||||
.spawn()
|
||||
.map_err(|error| format!("启动插件失败:{error}"))?;
|
||||
let stderr = match child.stderr.take() {
|
||||
Some(stderr) => stderr,
|
||||
None => {
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
return Err("插件 stderr 不可用".to_string());
|
||||
}
|
||||
};
|
||||
let stderr_summary = Arc::new(Mutex::new(String::new()));
|
||||
let stderr_summary_writer = Arc::clone(&stderr_summary);
|
||||
thread::spawn(move || {
|
||||
let mut bytes = Vec::new();
|
||||
let result = stderr
|
||||
.take((MAX_PLUGIN_STDERR_BYTES + 1) as u64)
|
||||
.read_to_end(&mut bytes);
|
||||
let summary = match result {
|
||||
Ok(_) if bytes.len() > MAX_PLUGIN_STDERR_BYTES => {
|
||||
"插件 stderr 超过大小上限".to_string()
|
||||
}
|
||||
Ok(_) => crate::sanitize_diagnostic_message(&String::from_utf8_lossy(&bytes), None)
|
||||
.chars()
|
||||
.take(1200)
|
||||
.collect(),
|
||||
Err(error) => format!("读取插件 stderr 失败:{error}"),
|
||||
};
|
||||
if let Ok(mut stored) = stderr_summary_writer.lock() {
|
||||
*stored = summary;
|
||||
}
|
||||
});
|
||||
#[cfg(windows)]
|
||||
let job = match crate::process_session::WindowsProcessJob::assign_std(&child) {
|
||||
Ok(job) => job,
|
||||
@@ -651,6 +682,7 @@ fn spawn_plugin(manifest: &PluginManifest, root: &Path) -> Result<RunningPlugin,
|
||||
lines: Some(receiver),
|
||||
pending: Arc::new(Mutex::new(BTreeMap::new())),
|
||||
registrations: Arc::new(Mutex::new(PluginRegistrations::default())),
|
||||
stderr_summary,
|
||||
next_request_id: 1,
|
||||
editor_context: None,
|
||||
active: Arc::new(AtomicBool::new(true)),
|
||||
@@ -982,9 +1014,27 @@ impl PluginHost {
|
||||
.as_mut()
|
||||
.and_then(|running| match running.child.try_wait() {
|
||||
Ok(Some(status)) => Some(if let Some(code) = status.code() {
|
||||
format!("插件进程已退出:exitCode={code}")
|
||||
format!(
|
||||
"插件进程已退出:exitCode={code}{}",
|
||||
running
|
||||
.stderr_summary
|
||||
.lock()
|
||||
.ok()
|
||||
.filter(|summary| !summary.is_empty())
|
||||
.map(|summary| format!(";stderr={summary}"))
|
||||
.unwrap_or_default()
|
||||
)
|
||||
} else {
|
||||
"插件进程已被信号终止".to_string()
|
||||
format!(
|
||||
"插件进程已被信号终止{}",
|
||||
running
|
||||
.stderr_summary
|
||||
.lock()
|
||||
.ok()
|
||||
.filter(|summary| !summary.is_empty())
|
||||
.map(|summary| format!(";stderr={summary}"))
|
||||
.unwrap_or_default()
|
||||
)
|
||||
}),
|
||||
Ok(None) => None,
|
||||
Err(error) => Some(format!("读取插件进程状态失败:{error}")),
|
||||
|
||||
@@ -100,3 +100,4 @@ DirectProject 回合失败在确认不是客户端内部不可归类故障时,
|
||||
- 发布构建预检原先把 `package.json` 元数据/读取/解析错误当成“没有 build”,把 `package-lock.json` 读取/解析错误当成“没有锁定版本”;现分别区分 NotFound 与真实读盘/JSON 错误,保留相对工作区路径和底层正文。
|
||||
- 发布尝试账本和工作区偏好文件原先把损坏、权限、类型和 JSON 解析失败当成空账本/默认偏好;现只有文件不存在才使用默认,其他读取错误沿发布幂等和建项目录命令返回具体正文。
|
||||
- 插件主机扫描工作区、读取进程状态、停止插件和禁用插件时原先吞掉目录项/`try_wait`/kill/wait 错误;现保留插件状态错误并阻止“停止失败却显示已禁用”,正常主动 kill 的非零退出码不再误报。
|
||||
- 插件进程 stderr 原先使用 `Stdio::null()`,即使进程退出也只能显示退出码;现以有界脱敏 sidecar 保留插件 stderr,并在退出状态中附带正文。
|
||||
|
||||
Reference in New Issue
Block a user