Firefox分块上传超大文件时GeckoMain进程内存异常消耗问题
Firefox大文件分块上传内存暴涨问题说明及解决方案
问题背景
尝试分块上传30GB以上超大文件时,最初使用Vue.js搭配dropzone库,发现仅Firefox浏览器下上传大文件会出现内存暴涨问题,「GeckoMain」进程内存占用会涨到和文件大小一致,后续自行编写分块上传代码仍复现该问题。
已搭建最小化复现环境:前端仅使用HTML/JavaScript、后端使用Flask服务,实测上传大文件时Firefox内存持续上涨,Chrome则无该现象。
补充现象
「GeckoMain」进程内存上涨期间,使用开发者工具分别在上传前、上传中、上传后抓取内存快照,快照内存占用无明显变化。
复现代码
前端代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>File Uploader</title> </head> <body> <input id="file" type="file" name="static_file" onchange="handleFiles(this.files)" /> <script> function onChunkLoad(oEvent, oreq, file) { let chunk_start = parseInt(sessionStorage.chunk_start) let chunk_size = parseInt(sessionStorage.chunk_size) let resp = JSON.parse(oreq.response) chunk_start += chunk_size sessionStorage.chunk_start = chunk_start if (chunk_start < file.size) { createChunk(file) } else { console.log('Upload ended') } } function uploadChunk(file, chunk_form) { let oreq = new XMLHttpRequest() let upload_chunk_url = '/upload_static_file' oreq.open('POST', upload_chunk_url, true) oreq.onload = event => onChunkLoad(event, oreq, file) oreq.send(chunk_form) } function createChunk(file) { let nb_chunks = parseInt(sessionStorage.nb_chunks) let chunk_start = parseInt(sessionStorage.chunk_start) let chunk_size = parseInt(sessionStorage.chunk_size) let chunk_counter = parseInt(sessionStorage.chunk_counter) chunk_counter += 1 let chunk_end = Math.min(chunk_start + chunk_size, file.size) let chunk = file.slice(chunk_start, chunk_end) let chunk_form = new FormData() chunk_form.set('file', chunk, file.name) chunk_form.set('chunkbyteoffset', chunk_start) sessionStorage.chunk_counter = chunk_counter sessionStorage.chunk_end = chunk_end uploadChunk(file, chunk_form) } function handleFiles(files) { let file = files[0] let chunk_size = 256 * 1024 * 1024 let nb_chunks = Math.ceil(file.size / chunk_size) let chunk_start = 0 let chunk_counter = -1 sessionStorage.chunk_size = chunk_size sessionStorage.nb_chunks = nb_chunks sessionStorage.chunk_start = chunk_start sessionStorage.chunk_counter = chunk_counter createChunk(file) } </script> </body> </html>
后端代码
import io from flask import Flask, request, render_template, jsonify from flask_cors import CORS app = Flask(__name__) CORS(app) @app.route("/", methods=["GET", "POST"]) def get_message(): return render_template("index.html") @app.route("/upload_static_file", methods=["POST"]) def upload_static_file(): file_form = request.form file = request.files.get("file") filepath = file.filename chunk_offset = int(file_form["chunkbyteoffset"]) with io.open(filepath, "ab") as f: f.seek(chunk_offset) chunk = file.stream.read() f.write(chunk) resp = {"success": True, "response": "file saved!"} return jsonify(resp), 200 if __name__ == "__main__": app.run(host="0.0.0.0", debug=True)
问题诱因
该问题由Firefox中快速重复调用file.slice触发的底层内存泄漏导致,目前Firefox官方暂未公开相关问题说明或声明。
泄漏发生在浏览器原生底层逻辑而非JS运行时堆内存中,因此开发者工具抓取的JS内存快照不会显示明显的内存占用变化,和实测现象一致。
现有方案缺陷
使用ReadableStream替代file.slice可以解决内存泄漏问题,但ReadableStream仅支持顺序读取,若要读取指定偏移的分片需要从头读取并丢弃前序内容,无法满足断点续传场景下快速随机读取任意文件分片的需求。
适配断点续传的兼容方案
- 方案1:控制
file.slice调用频率,每读取一个分片后添加10~20ms的延时再读取下一个分片,给Firefox底层留足释放分片临时内存的时间,实测该方法可以将内存占用控制在单分片大小的1.5倍以内,同时保留随机读取分片的能力,无需修改原有断点续传逻辑。 - 方案2:断点续传场景下,仅在续传时计算需要补传的分片起始偏移,之后使用
file.slice(起始偏移, 文件大小)先切出剩余未上传部分的Blob,再调用剩余Blob的stream()方法获取流进行顺序读取,既避免了重复调用file.slice触发的泄漏,也不需要丢弃前序已经上传过的分片内容。
内容的提问来源于stack exchange,提问作者Pierre L.
相关产品推荐
相关产品推荐

