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

单文件多ReadStream遇EBADF错误,如何维持文件描述符开启

问题

我需要处理一个包含头部信息与多段二进制数据的大文件,其中头部信息定义了各二进制数据段的偏移量和长度。我计划采用以下处理流程:

  • 使用fs.openSync()获取文件描述符
  • 创建ReadStream读取头部信息(以获取二进制数据段的偏移量),该流配置autoClose: false
  • 关闭该“头部”ReadStream
  • 创建多个ReadStream分别读取各二进制数据段,每个流设置独立的start和end参数,且均配置autoClose: false
  • 所有处理完成后手动关闭文件描述符

但在测试该方案时,关闭头部流后创建第二个流时触发了如下错误:
Uncaught Error Error: EBADF: bad file descriptor, read
这表明文件描述符已无法用于读取操作,尽管所有流都设置了autoClose: false。

请问如何确保文件描述符保持开启状态,直到手动执行关闭操作?

测试代码如下:

(async function () {
  // open the file for reading
  const fd = fs.openSync("c:\\some\\large\\file.txt", "r");

  // initial stream that will extract the header info
  const initStream = fs.createReadStream(null, {
    fd,
    autoClose: false,
  });

  // header info data
  const headerContent = await extractHeaderContent(initStream);

  // for test purpose
  // try and extract the header again
  const testSecondStream1 = fs.createReadStream(null, {
    fd,
    autoClose: false,
  });

  const testHeaderContent = await extractHeaderContent(initStream);

  fs.closeSync(fd);
})();

// stream the data until the header data is retrieved
async function extractHeaderContent(initStream) {
  return new Promise((resolve, reject) => {
    let content = "";

    initStream.on("data", (chunk) => {
      if (chunk.indexOf("EndHeader") > -1) {
        content += chunk.toString();
        let d = content.split("EndHeader")[0] + "EndHeader";

        // once the header info is extracted - close the stream
        initStream.close(() => resolve(d));
      }

      content += chunk.toString();
    });
  });
}
解决方案

问题根源

核心问题在于:即便设置了autoClose: false,调用ReadStream.close()方法时,Node.js仍会销毁关联的文件描述符。这是因为当你传入自定义fd时,流默认会认为自己拥有该fd的管理权,autoClose: false仅阻止流在正常结束/出错时自动关闭fd,但close()方法本身的逻辑就是关闭fd并销毁流。

解决步骤

要完全掌控fd的生命周期,需同时配置两个参数,并调整流的终止方式:

  1. 给所有基于自定义fd创建的ReadStream同时添加autoClose: false和emitClose: false配置
    • autoClose: false:阻止流在正常结束/出错时自动关闭fd
    • emitClose: false:阻止流在销毁时触发fd的关闭操作
  2. 不要调用stream.close(),改用stream.destroy()终止流,避免触发fd关闭
  3. 修正测试代码中的逻辑错误:第二次提取头部时要传入新创建的流,而非已终止的旧流

修正后的测试代码

const fs = require('fs');

(async function () {
  // 打开文件获取fd
  const fd = fs.openSync("c:\\some\\large\\file.txt", "r");

  // 同时配置autoClose和emitClose为false
  const initStream = fs.createReadStream(null, {
    fd,
    autoClose: false,
    emitClose: false
  });

  const headerContent = await extractHeaderContent(initStream);
  console.log('首次提取头部:', headerContent);

  // 第二个流同样配置两个false
  const testSecondStream1 = fs.createReadStream(null, {
    fd,
    autoClose: false,
    emitClose: false
  });

  // 传入新创建的流,而非旧的initStream
  const testHeaderContent = await extractHeaderContent(testSecondStream1);
  console.log('二次提取头部:', testHeaderContent);

  // 所有流处理完成后手动关闭fd
  fs.closeSync(fd);
})();

async function extractHeaderContent(stream) {
  return new Promise((resolve, reject) => {
    let content = "";

    stream.on("data", (chunk) => {
      content += chunk.toString();
      // 检查是否已读取到头部结束标记
      if (content.includes("EndHeader")) {
        const header = content.split("EndHeader")[0] + "EndHeader";
        // 销毁流但不关闭fd
        stream.destroy();
        resolve(header);
      }
    });

    // 处理流读取错误
    stream.on("error", (err) => {
      reject(err);
    });
  });
}

额外说明

  • 所有基于同一个fd创建的流,都必须保持autoClose: false和emitClose: false的配置,否则任意一个流的关闭操作都会导致fd失效
  • 务必在所有流处理完成后手动调用fs.closeSync()或fs.close()关闭fd,避免资源泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 10:35:24