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

Docker容器执行编译文件后输出读取失败问题排查与解决

解决Docker容器执行C++代码后读取输出文件出现ENOENT错误的问题

问题背景

我开发了一个支持文件上传的网页,用户提交C++文件后,服务端会启动Docker容器编译并运行代码,最终在网页显示程序输出。最初遇到输出包含不可打印字符的问题,改为将输出写入容器内的文本文件后,又出现Node.js读取文件时的ENOENT错误:

Error reading the file: [Error: ENOENT: no such file or directory, open '/home/slava/Desktop/TESTING/output/output.txt'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: '/home/slava/Desktop/TESTING/output/output.txt'
}

注释掉容器停止和删除的代码后输出正常,但业务要求每次使用后必须清理容器。观察发现文件已在主机创建,但Node.js要到第二次提交才能识别,问题与容器清理时机直接相关。

问题根源

  1. 异步操作顺序错误:fs.readFile是异步非阻塞调用,代码触发文件读取后立刻执行容器停止/删除,此时文件可能还未完全从容器同步到主机挂载卷,Node.js无法感知文件存在。
  2. Docker卷缓存机制:容器内的文件写入可能被操作系统缓存,未立刻同步到主机文件系统,容器停止后缓存未刷新导致主机无法读取文件。
  3. 文件覆盖风险:所有用户输出都写入同一个output.txt,并发场景下会出现读写冲突或文件覆盖。

解决方案

1. 修正异步操作顺序,确保读取完成后再清理容器

将异步的fs.readFile替换为Promise版的fs.promises.readFile,用await等待读取完成后再执行容器停止和删除,保证文件读取完成后再销毁容器。

2. 强制同步容器内文件到主机

在entrypoint.sh的文件写入完成后添加sync命令,强制操作系统将缓存数据写入磁盘,确保主机能立刻获取到文件内容。

3. 避免文件覆盖,生成用户独立输出文件

用用户名作为输出文件的后缀(如output_${username}.txt),防止多用户提交时互相覆盖输出内容。

4. 移除Dockerfile中不必要的COPY操作

Dockerfile中的COPY submissions/ /submissions/会覆盖挂载的卷内容,应删除该指令,因为我们已经通过HostConfig绑定了主机的submissions目录到容器。

修改后的关键代码

修改后的server.js核心逻辑

app.post('/execute', upload.single('file'), async (req, res) => {
    const { code, username } = req.body;

    if (req.file) {
        const userDirectory = `/submissions/${username}`;
        const uploadedFilePath = path.join(userDirectory, req.file.originalname);
        const codeFileName = path.basename(uploadedFilePath, path.extname(uploadedFilePath));

        const container = await docker.createContainer({
            Image: 'my-cpp-app',
            Tty: true,
            Detach: true,
            HostConfig: {
                Binds: [
                    `${__dirname}/submissions:/submissions`,
                    `${__dirname}/output:/output`,
                ],
            },
        });

        await container.start();

        const sourceCodeFile = `/submissions/${username}/${req.file.originalname}`;
        // 传递用户名给entrypoint.sh,生成独立输出文件
        const command = `/entrypoint.sh "${sourceCodeFile}" "${username}"`;

        const exec = await container.exec({
            Cmd: ['bash', '-c', command]
        });

        const execResult = await exec.start();
        
        if (execResult.output) {
            await new Promise((resolve, reject) => {
                container.modem.demuxStream(execResult.output, process.stdout, process.stderr);
                execResult.output.on('end', resolve);
                execResult.output.on('error', reject);
            });
        }
        
        // 使用Promise版readFile,等待读取完成后再清理容器
        try {
            const data = await fs.promises.readFile(`${__dirname}/output/output_${username}.txt`, 'utf8');
            res.send({output: data});
        } catch (err) {
            console.error('Error reading the file:', err);
            res.status(500).send({output: '读取输出文件失败'});
        }
        
        // 读取完成后再执行容器清理
        await container.stop();
        await container.remove();
    }
});

修改后的entrypoint.sh

#!/bin/bash

if [ $# -ne 2 ]; then
    echo "Usage: $0 <source_code_file.cpp> <username>"
    exit 1
fi

g++ -o user_program "$1" 

if [ $? -eq 0 ]; then
    ./user_program > /output/output_$2.txt
    # 强制同步文件到磁盘,确保主机能立刻读取
    sync
else
    echo "Compilation failed." >&2
    echo "Compilation failed." > /output/output_$2.txt
    sync
fi

修改后的Dockerfile

FROM ubuntu:20.04

# Set environment variables to avoid interactive prompts during installation
ENV DEBIAN_FRONTEND=noninteractive

# Update and install essential packages
RUN apt-get update -y && apt-get install -y \
    build-essential \
    cmake \
    gdb \
    g++ 

RUN mkdir /output && chmod 777 /output

# 移除不必要的COPY操作,避免覆盖挂载卷
# COPY submissions/ /submissions/ 

# Define an entry point script that will be executed by the Node.js server
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

额外注意事项

  • 确保主机的output目录已提前创建,并设置正确的读写权限(如chmod 777 output)。
  • 可添加输出文件的清理逻辑,避免磁盘空间被大量输出文件占用(如定时删除7天前的文件)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 04:24:55