You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go中exec.CommandContext执行yarn install时,设置Stdout/Stderr后超时失效

问题分析

当用exec.CommandContext执行yarn install并搭配自定义LogWriter捕获输出时,超时功能失效的核心原因有两点:

  • 子进程可能因stdout/stderr管道阻塞,无法及时响应context超时发出的终止信号:如果父进程的Writer处理输出速度跟不上子进程的输出速度,子进程会卡在IO写操作上,此时即使收到终止信号也无法立即退出,直到IO操作完成。
  • 自定义LogWriter未感知context取消:即使context已经超时,Write方法仍会继续处理输出,导致exec包后台处理输出的goroutine持续运行,cmd.Run()会等待这些goroutine完成后才返回,看起来像是子进程一直在执行。
解决方案

方案1:在LogWriter中检查context状态

修改LogWriter的Write方法,每次处理输出前先检查context是否已取消,若取消则直接返回错误,让exec的后台goroutine停止处理输出:

import (
    "context"
    "bytes"
    "log"
    // 导入你的logging包
)

// LogWriter 自定义日志输出类型,携带context用于感知取消
type LogWriter struct {
    ctx context.Context
}

func (c LogWriter) Write(p []byte) (int, error) {
    // 优先检查context是否已取消
    select {
    case <-c.ctx.Done():
        return 0, c.ctx.Err()
    default:
    }

    // 原有日志输出逻辑
    logging.Infof(c.ctx, string(p))
    
    // 原代码中的scanner未实际使用,可直接删除以减少冗余操作
    return len(p), nil
}

方案2:手动管控进程与输出处理

放弃cmd.Run(),改用cmd.Start()手动启动进程,同时启动goroutine单独处理输出,主动监控context取消事件并终止进程:

import (
    "context"
    "os"
    "log"
    "bufio"
    "os/exec"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.TODO(), 60*time.Second)
    defer cancel()

    cmd := exec.CommandContext(ctx, "yarn", "install")
    cmd.Dir = "/project/"

    // 创建stdout/stderr管道
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        log.Fatal(err)
    }
    stderr, err := cmd.StderrPipe()
    if err != nil {
        log.Fatal(err)
    }

    // 启动goroutine处理stdout
    go func() {
        scanner := bufio.NewScanner(stdout)
        for scanner.Scan() {
            select {
            case <-ctx.Done():
                return
            default:
                logging.Infof(ctx, scanner.Text())
            }
        }
        if err := scanner.Err(); err != nil {
            log.Printf("stdout扫描错误: %v", err)
        }
    }()

    // 启动goroutine处理stderr
    go func() {
        scanner := bufio.NewScanner(stderr)
        for scanner.Scan() {
            select {
            case <-ctx.Done():
                return
            default:
                logging.Infof(ctx, scanner.Text())
            }
        }
        if err := scanner.Err(); err != nil {
            log.Printf("stderr扫描错误: %v", err)
        }
    }()

    // 启动子进程
    if err := cmd.Start(); err != nil {
        log.Fatal(err)
    }

    // 等待进程结束或context取消
    select {
    case <-ctx.Done():
        // 超时后主动杀死进程
        if err := cmd.Process.Kill(); err != nil {
            log.Printf("杀死进程失败: %v", err)
        }
        // 等待进程退出
        if err := cmd.Wait(); err != nil {
            log.Printf("进程退出异常: %v", err)
        }
        os.Exit(1)
    default:
        // 等待进程正常结束
        if err := cmd.Wait(); err != nil {
            log.Printf("进程执行失败: %v", err)
            os.Exit(1)
        }
    }

    os.Exit(0)
}

额外注意事项

  • 避免在Write方法中执行耗时操作(比如同步写入磁盘的日志操作),否则会加剧子进程的IO阻塞,延迟信号响应。
  • 如果yarn install输出量极大,建议对日志做批量处理,或使用异步日志库,降低Write方法的执行耗时。

内容的提问来源于stack exchange,提问作者ARKhan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 17:13:11