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

Golang中Run()执行命令无返回,音乐流服务请求阻塞问题求助

问题描述

尝试用youtube-dl和ffmpeg搭建音乐流服务,基于Gin框架处理POST请求:当用户发送携带视频URL的请求时,通过io.Pipe连接youtube-dl和ffmpeg,将视频转成MP3流返回。但测试时请求始终无法完成,日志不输出"Done-----------------------",推测进程阻塞。

相关代码如下:

router.POST("/submit", func(c *gin.Context) {
    body := Body{}
    if err := c.BindJSON(&body); err != nil {
        c.AbortWithError(http.StatusBadRequest, err)
        return
    }
    w := c.Writer
    header := w.Header()
    header.Set("Content-Type", "audio/mp3")
    w.WriteHeader(http.StatusOK)
    fetchMusic(c, body.Data)
})

func fetchMusic(c *gin.Context, data string) {
    r, w := io.Pipe()
    defer r.Close()

    ydl := exec.Command("youtube-dl", data, "-o-")
    ffmpeg := exec.Command("ffmpeg", "-i", "/dev/stdin", "-vn", "-f", "mp3", "-")

    ydl.Stdout = w
    ydl.Stderr = os.Stderr

    ffmpeg.Stdin = r
    ffmpeg.Stdout = c.Writer
    ffmpeg.Stderr = os.Stderr
    fmt.Println("Starting-----------------------")
    go func() {
        if err := ydl.Run(); err != nil {
            panic(err)
        }
    }()
    
    if err := ffmpeg.Run(); err != nil {
        panic(err)
    }

    fmt.Println("Done-----------------------")
}

问题分析与解决

核心问题:io.Pipe的Writer未关闭

io.Pipe的读写端是同步阻塞的:当youtube-dl执行完成后,你没有关闭w(Pipe的Writer),导致ffmpeg一直认为还有输入数据,会持续阻塞在读取stdin的状态,永远不会退出,所以ffmpeg.Run()无法完成,自然不会打印"Done"。

修复步骤

  1. 关闭Pipe的Writer:在youtube-dl执行完成(无论成功或失败)后,必须关闭w,让ffmpeg知道输入流已经结束。
  2. 替换panic为合理的错误处理:直接panic会导致整个服务崩溃,应该用日志记录错误,或者通过Gin上下文返回错误给客户端。
  3. 监听请求取消信号(可选但推荐):如果客户端中途断开连接,终止子进程,避免资源浪费。

修改后的代码

import (
    "io"
    "os/exec"
    "log"
    "net/http"
    "github.com/gin-gonic/gin"
)

router.POST("/submit", func(c *gin.Context) {
    body := Body{}
    if err := c.BindJSON(&body); err != nil {
        c.AbortWithError(http.StatusBadRequest, err)
        return
    }
    c.Writer.Header().Set("Content-Type", "audio/mp3")
    c.Status(http.StatusOK)
    
    if err := fetchMusic(c, body.Data); err != nil {
        log.Printf("处理音乐流失败: %v", err)
        // 如果还没开始写响应,可以返回错误;否则只能记录日志
        if !c.Writer.Written() {
            c.AbortWithError(http.StatusInternalServerError, err)
        }
    }
})

func fetchMusic(c *gin.Context, data string) error {
    r, w := io.Pipe()
    defer r.Close()
    // 确保Writer最终关闭
    defer w.Close()

    ydl := exec.Command("youtube-dl", data, "-o-")
    ffmpeg := exec.Command("ffmpeg", "-i", "/dev/stdin", "-vn", "-f", "mp3", "-")

    ydl.Stdout = w
    ydl.Stderr = os.Stderr

    ffmpeg.Stdin = r
    ffmpeg.Stdout = c.Writer
    ffmpeg.Stderr = os.Stderr

    log.Println("Starting-----------------------")

    // 启动youtube-dl的goroutine,执行完成后关闭Writer
    go func() {
        err := ydl.Run()
        // 必须关闭Writer,即使youtube-dl执行失败
        if closeErr := w.Close(); closeErr != nil {
            log.Printf("关闭Pipe Writer失败: %v", closeErr)
        }
        if err != nil {
            log.Printf("youtube-dl执行失败: %v", err)
        }
    }()

    // 绑定请求取消信号,客户端断开时终止子进程
    ctx := c.Request.Context()
    go func() {
        <-ctx.Done()
        _ = ydl.Process.Kill()
        _ = ffmpeg.Process.Kill()
    }()

    // 等待ffmpeg执行完成
    if err := ffmpeg.Run(); err != nil {
        return err
    }

    log.Println("Done-----------------------")
    return nil
}

关键说明

  • 关闭Pipe Writer:defer w.Close()确保无论流程如何,Writer都会被关闭;同时在youtube-dl执行完后主动关闭,让ffmpeg及时感知输入结束。
  • 错误处理:用日志记录错误,避免panic导致服务崩溃;同时检查响应是否已写入,决定是否返回错误给客户端。
  • 请求取消监听:通过c.Request.Context()监听客户端断开事件,及时终止子进程,防止僵尸进程占用资源。

内容的提问来源于stack exchange,提问作者H.Okan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 20:52:08