PR Review 2026-05-31 16:59:07

refactor(sandbox): 重构进程管理实现跨平台支持 #15

Luo-root/PRism · opened by Luo-root

Change Summary
3 files · +53 · -37

本次 PR 将 process.go 中的 Windows 特定代码(进程管理、进程树 kill)拆分到 build tag 隔离的 platform 文件中,实现了跨平台进程管理的重构。核心思路是将 setupProcess() 和 killProcessTree() 抽象为平台特定函数,通过 //go:build tag 在编译期选择正确实现。

Impact 影响 sandbox 包的进程管理、进程终止和默认语言配置,涉及所有平台的执行行为
2
Critical
1
Warning
1
Suggestion
1
Nitpick
Critical 2
Critical high internal/sandbox/process.go:34 DefaultLangs() 移除了 Windows 语言配置,导致 Windows 上功能回归

原代码中 DefaultLangs() 对 runtime.GOOS == "windows" 有独立的语言配置分支,使用 `python`(非 python3)、`cmd /C`(非 bash -c)。重构时只移除了 Windows 分支,但未将 DefaultLangs 也通过 build tag 做跨平台分离。当前 Windows 编译后将使用 Unix 配置:`python3`(Windows 通常只有 `python`)和 `bash -c`(Windows 默认无 bash),导致 Python 和 Shell 执行在 Windows 上完全不可用。这是一个回归 Bug——原代码能正确运行于 Windows,重构后反而破坏了。

Current
func DefaultLangs() map[string]LangConfig {
	return map[string]LangConfig{
		"python": {Command: "python3", Ext: ".py"},
		"node":   {Command: "node", Ext: ".js"},
		"go": {
			Command: "go", Args: []string{"run"}, Ext: ".go",
			InitFiles: map[string]string{"go.mod": "module sandbox\ngo 1.21\n"},
		},
		"shell": {Command: "bash", Args: []string{"-c"}},
	}
}
Suggested Fix
// 在 process.go 中保留为 build tag 中立版本(如果各平台一致部分)
// 推荐方案:将 DefaultLangs 也拆分为平台特定文件

// process_langs_unix.go (//go:build linux || darwin)
func DefaultLangs() map[string]LangConfig {
	return map[string]LangConfig{
		"python": {Command: "python3", Ext: ".py"},
		"node":   {Command: "node", Ext: ".js"},
		"go": {
			Command: "go", Args: []string{"run"}, Ext: ".go",
			InitFiles: map[string]string{"go.mod": "module sandbox\ngo 1.21\n"},
		},
		"shell": {Command: "bash", Args: []string{"-c"}},
	}
}

// process_langs_windows.go (//go:build windows)
func DefaultLangs() map[string]LangConfig {
	return map[string]LangConfig{
		"python": {Command: "python", Ext: ".py"},
		"node":   {Command: "node", Ext: ".js"},
		"go": {
			Command: "go", Args: []string{"run"}, Ext: ".go",
			InitFiles: map[string]string{"go.mod": "module sandbox\ngo 1.21\n"},
		},
		"shell": {Command: "cmd", Args: []string{"/C"}},
	}
}
Critical medium internal/sandbox/process_unix.go:11 Unix setupProcess 为空函数,缺少 WaitDelay 兜底,存在进程 I/O 阻塞风险

process_unix.go 的 setupProcess() 是空函数,未设置 cmd.Cancel 和 cmd.WaitDelay。当 context 被取消时,Go 默认行为仅发送 os.Kill 给主进程(不保证杀子进程),且没有 WaitDelay 兜底。如果子进程的 stdout/stderr 管道仍被其他子进程持有(例如管道泄漏),Wait() 将永远阻塞。原 Windows 实现中设置了 `WaitDelay: 3 * time.Second` 来防御此场景,Unix 端应同等处理。此外,空的 killProcessTree 仅 p.Kill() 单进程,不处理子进程树,对执行 shell 脚本等会 fork 子进程的场景无效。

Current
func setupProcess(cmd *exec.Cmd) {
	// Unix 系统不需要特殊设置
}

func killProcessTree(p *os.Process) {
	if p != nil {
		p.Kill()
	}
}
Suggested Fix
func setupProcess(cmd *exec.Cmd) {
	// 设置进程组,便于后续整组 kill
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
	}
	cmd.Cancel = func() error {
		killProcessTree(cmd.Process)
		return nil
	}
	// 防止 I/O 阻塞导致 Wait() 永远不返回
	cmd.WaitDelay = 3 * time.Second
}

func killProcessTree(p *os.Process) {
	if p == nil {
		return
	}
	// 向进程组发送 SIGKILL,确保子进程也被终止
	pgid, err := syscall.Getpgid(p.Pid)
	if err == nil {
		syscall.Kill(-pgid, syscall.SIGKILL)
	} else {
		p.Kill()
	}
}
Warning 1
Suggestion 1
Nitpick 1