簡體   English   中英

節點和錯誤:EMFILE,打開的文件太多

[英]node and Error: EMFILE, too many open files

幾天來,我一直在尋找解決錯誤的有效解決方案

錯誤:EMFILE,打開的文件太多

似乎很多人都有同樣的問題。 通常的答案涉及增加文件描述符的數量。 所以,我試過這個:

sysctl -w kern.maxfiles=20480

默認值是10240。這在我看來有點奇怪,因為我在目錄中處理的文件數在10240以下。更奇怪的是,我增加文件描述符數后仍然收到同樣的錯誤.

第二個問題:

經過多次搜索,我找到了解決“打開文件太多”問題的方法:

var requestBatches = {};
function batchingReadFile(filename, callback) {
  // First check to see if there is already a batch
  if (requestBatches.hasOwnProperty(filename)) {
    requestBatches[filename].push(callback);
    return;
  }

  // Otherwise start a new one and make a real request
  var batch = requestBatches[filename] = [callback];
  FS.readFile(filename, onRealRead);
  
  // Flush out the batch on complete
  function onRealRead() {
    delete requestBatches[filename];
    for (var i = 0, l = batch.length; i < l; i++) {
      batch[i].apply(null, arguments);
    }
  }
}

function printFile(file){
    console.log(file);
}

dir = "/Users/xaver/Downloads/xaver/xxx/xxx/"

var files = fs.readdirSync(dir);

for (i in files){
    filename = dir + files[i];
    console.log(filename);
    batchingReadFile(filename, printFile);

不幸的是,我仍然收到同樣的錯誤。 這段代碼有什么問題?

優雅的 fs不起作用時……或者您只想了解泄漏的來源。 按照這個過程。

(例如,如果您的問題與套接字有關,graceful-fs 不會修復您的旅行車。)

來自我的博客文章: http : //www.blakerobertson.com/devlog/2014/1/11/how-to-determine-whats-causing-error-connect-emfile-nodejs.html

如何隔離

此命令將輸出 nodejs 進程的打開句柄數:

lsof -i -n -P | grep nodejs
COMMAND     PID    USER   FD   TYPE    DEVICE SIZE/OFF NODE NAME
...
nodejs    12211    root 1012u  IPv4 151317015      0t0  TCP 10.101.42.209:40371->54.236.3.170:80 (ESTABLISHED)
nodejs    12211    root 1013u  IPv4 151279902      0t0  TCP 10.101.42.209:43656->54.236.3.172:80 (ESTABLISHED)
nodejs    12211    root 1014u  IPv4 151317016      0t0  TCP 10.101.42.209:34450->54.236.3.168:80 (ESTABLISHED)
nodejs    12211    root 1015u  IPv4 151289728      0t0  TCP 10.101.42.209:52691->54.236.3.173:80 (ESTABLISHED)
nodejs    12211    root 1016u  IPv4 151305607      0t0  TCP 10.101.42.209:47707->54.236.3.172:80 (ESTABLISHED)
nodejs    12211    root 1017u  IPv4 151289730      0t0  TCP 10.101.42.209:45423->54.236.3.171:80 (ESTABLISHED)
nodejs    12211    root 1018u  IPv4 151289731      0t0  TCP 10.101.42.209:36090->54.236.3.170:80 (ESTABLISHED)
nodejs    12211    root 1019u  IPv4 151314874      0t0  TCP 10.101.42.209:49176->54.236.3.172:80 (ESTABLISHED)
nodejs    12211    root 1020u  IPv4 151289768      0t0  TCP 10.101.42.209:45427->54.236.3.171:80 (ESTABLISHED)
nodejs    12211    root 1021u  IPv4 151289769      0t0  TCP 10.101.42.209:36094->54.236.3.170:80 (ESTABLISHED)
nodejs    12211    root 1022u  IPv4 151279903      0t0  TCP 10.101.42.209:43836->54.236.3.171:80 (ESTABLISHED)
nodejs    12211    root 1023u  IPv4 151281403      0t0  TCP 10.101.42.209:43930->54.236.3.172:80 (ESTABLISHED)
....

請注意: 1023u(最后一行) - 這是默認最大值的第 1024 個文件句柄。

現在,看看最后一列。 這表明哪個資源是開放的。 您可能會看到許多行都具有相同的資源名稱。 希望現在可以告訴您在代碼中查找泄漏的位置。

如果您不知道多個節點進程,請先查找哪個進程的 pid 為 12211。它會告訴您進程。

在我上面的例子中,我注意到有一堆非常相似的 IP 地址。 他們都是54.236.3.###通過進行 IP 地址查找,能夠確定在我的情況下它與 pubnub 相關。

命令參考

使用此語法來確定進程打開了多少個打開的句柄...

獲取某個 pid 的打開文件數

我使用此命令來測試在我的應用程序中執行各種事件后打開的文件數。

lsof -i -n -P | grep "8465" | wc -l
# lsof -i -n -P | grep "nodejs.*8465" | wc -l
28
# lsof -i -n -P | grep "nodejs.*8465" | wc -l
31
# lsof -i -n -P | grep "nodejs.*8465" | wc -l
34

你的進程限制是多少?

ulimit -a

您想要的行將如下所示:

open files                      (-n) 1024

永久更改限制:

  • 在 Ubuntu 14.04、nodejs v. 7.9 上測試

如果您希望打開許多連接(websockets 就是一個很好的例子),您可以永久增加限制:

  • 文件:/etc/pam.d/common-session (添加到最后)

     session required pam_limits.so
  • 文件:/etc/security/limits.conf (添加到最后,如果已經存在則編輯)

     root soft nofile 40000 root hard nofile 100000
  • 重新啟動您的 nodejs 並從 ssh 注銷/登錄。

  • 這可能不適用於較舊的 NodeJS,您需要重新啟動服務器

  • 如果您的節點使用不同的 uid 運行,請使用代替。

使用 Isaac Schlueter(node.js 維護者)的graceful-fs模塊可能是最合適的解決方案。 如果遇到 EMFILE,它會進行增量回退。 它可以用作內置fs模塊的直接替代品。

我不確定這是否會幫助任何人,我開始從事一個有很多依賴項的大項目,這給了我同樣的錯誤。 我的同事建議我使用 brew 安裝watchman ,這為我解決了這個問題。

brew update
brew install watchman

2019 年 6 月 26 日編輯: Github 鏈接到守望者

您正在閱讀太多文件。 節點異步讀取文件,它將一次讀取所有文件。 因此,您可能正在閱讀 10240 限制。

看看這是否有效:

var fs = require('fs')
var events = require('events')
var util = require('util')
var path = require('path')

var FsPool = module.exports = function(dir) {
    events.EventEmitter.call(this)
    this.dir = dir;
    this.files = [];
    this.active = [];
    this.threads = 1;
    this.on('run', this.runQuta.bind(this))
};
// So will act like an event emitter
util.inherits(FsPool, events.EventEmitter);

FsPool.prototype.runQuta = function() {
    if(this.files.length === 0 && this.active.length === 0) {
        return this.emit('done');
    }
    if(this.active.length < this.threads) {
        var name = this.files.shift()

        this.active.push(name)
        var fileName = path.join(this.dir, name);
        var self = this;
        fs.stat(fileName, function(err, stats) {
            if(err)
                throw err;
            if(stats.isFile()) {
                fs.readFile(fileName, function(err, data) {
                    if(err)
                        throw err;
                    self.active.splice(self.active.indexOf(name), 1)
                    self.emit('file', name, data);
                    self.emit('run');

                });
            } else {
                self.active.splice(self.active.indexOf(name), 1)
                self.emit('dir', name);
                self.emit('run');
            }
        });
    }
    return this
};
FsPool.prototype.init = function() {
    var dir = this.dir;
    var self = this;
    fs.readdir(dir, function(err, files) {
        if(err)
            throw err;
        self.files = files
        self.emit('run');
    })
    return this
};
var fsPool = new FsPool(__dirname)

fsPool.on('file', function(fileName, fileData) {
    console.log('file name: ' + fileName)
    console.log('file data: ', fileData.toString('utf8'))

})
fsPool.on('dir', function(dirName) {
    console.log('dir name: ' + dirName)

})
fsPool.on('done', function() {
    console.log('done')
});
fsPool.init()

我今天遇到了這個問題,沒有找到好的解決方案,我創建了一個模塊來解決它。 我受到@fbartho 片段的啟發,但想避免覆蓋 fs 模塊。

我寫的模塊是Filequeue ,你可以像 fs 一樣使用它:

var Filequeue = require('filequeue');
var fq = new Filequeue(200); // max number of files to open at once

fq.readdir('/Users/xaver/Downloads/xaver/xxx/xxx/', function(err, files) {
    if(err) {
        throw err;
    }
    files.forEach(function(file) {
        fq.readFile('/Users/xaver/Downloads/xaver/xxx/xxx/' + file, function(err, data) {
            // do something here
        }
    });
});

像我們所有人一樣,您也是異步 I/O 的受害者。 對於異步調用,如果你循環了很多文件,Node.js 將開始為每個要讀取的文件打開一個文件描述符,然后等待操作直到你關閉它。

文件描述符保持打開狀態,直到您的服務器上有資源可以讀取它。 即使您的文件很小並且讀取或更新速度很快,也需要一些時間,但同時您的循環不會停止打開新的文件描述符。 所以如果你有太多的文件,很快就會達到限制,你會得到一個漂亮的EMFILE

有一個解決方案,創建一個隊列來避免這種影響。

感謝編寫Async 的人,有一個非常有用的功能。 有一個名為Async.queue的方法,您可以創建一個具有限制的新隊列,然后將文件名添加到隊列中。

注意:如果您必須打開許多文件,最好存儲當前打開的文件並且不要無限地重新打開它們。

const fs = require('fs')
const async = require("async")

var q = async.queue(function(task, callback) {
    console.log(task.filename);
    fs.readFile(task.filename,"utf-8",function (err, data_read) {
            callback(err,task.filename,data_read);
        }
    );
}, 4);

var files = [1,2,3,4,5,6,7,8,9,10]

for (var file in files) {
    q.push({filename:file+".txt"}, function (err,filename,res) {
        console.log(filename + " read");
    });
}

您可以看到每個文件都添加到隊列中(console.log 文件名),但僅當當前隊列低於您之前設置的限制時。

async.queue 通過回調獲取有關隊列可用性的信息,只有在讀取數據文件並且您必須執行的任何操作都完成時才會調用此回調。 (見 fileRead 方法)

所以你不會被文件描述符淹沒。

> node ./queue.js
0.txt
    1.txt
2.txt
0.txt read
3.txt
3.txt read
4.txt
2.txt read
5.txt
4.txt read
6.txt
5.txt read
7.txt
    1.txt read (biggest file than other)
8.txt
6.txt read
9.txt
7.txt read
8.txt read
9.txt read

我為同樣的問題做了上面提到的所有東西,但沒有任何效果。 我在下面試過它 100% 工作。 簡單的配置更改。

選項 1 設置限制(大多數情況下不起作用)

user@ubuntu:~$ ulimit -n 65535

檢查可用限制

user@ubuntu:~$ ulimit -n
1024

選項 2 增加可用限制為 65535

user@ubuntu:~$ sudo nano /etc/sysctl.conf

向其中添加以下行

fs.file-max = 65535

運行它以使用新配置刷新

user@ubuntu:~$ sudo sysctl -p

編輯以下文件

user@ubuntu:~$ sudo vim /etc/security/limits.conf

添加以下幾行

root soft     nproc          65535    
root hard     nproc          65535   
root soft     nofile         65535   
root hard     nofile         65535

編輯以下文件

user@ubuntu:~$ sudo vim /etc/pam.d/common-session

將此行添加到其中

session required pam_limits.so

注銷並登錄並嘗試以下命令

user@ubuntu:~$ ulimit -n
65535

選項 3 只需添加以下行

DefaultLimitNOFILE=65535

到 /etc/systemd/system.conf 和 /etc/systemd/user.conf

我剛剛寫了一小段代碼來自己解決這個問題,所有其他解決方案都顯得過於重量級,需要您更改程序結構。

此解決方案只是停止任何 fs.readFile 或 fs.writeFile 調用,以便在任何給定時間都不會超過設定的數量。

// Queuing reads and writes, so your nodejs script doesn't overwhelm system limits catastrophically
global.maxFilesInFlight = 100; // Set this value to some number safeish for your system
var origRead = fs.readFile;
var origWrite = fs.writeFile;

var activeCount = 0;
var pending = [];

var wrapCallback = function(cb){
    return function(){
        activeCount--;
        cb.apply(this,Array.prototype.slice.call(arguments));
        if (activeCount < global.maxFilesInFlight && pending.length){
            console.log("Processing Pending read/write");
            pending.shift()();
        }
    };
};
fs.readFile = function(){
    var args = Array.prototype.slice.call(arguments);
    if (activeCount < global.maxFilesInFlight){
        if (args[1] instanceof Function){
            args[1] = wrapCallback(args[1]);
        } else if (args[2] instanceof Function) {
            args[2] = wrapCallback(args[2]);
        }
        activeCount++;
        origRead.apply(fs,args);
    } else {
        console.log("Delaying read:",args[0]);
        pending.push(function(){
            fs.readFile.apply(fs,args);
        });
    }
};

fs.writeFile = function(){
    var args = Array.prototype.slice.call(arguments);
    if (activeCount < global.maxFilesInFlight){
        if (args[1] instanceof Function){
            args[1] = wrapCallback(args[1]);
        } else if (args[2] instanceof Function) {
            args[2] = wrapCallback(args[2]);
        }
        activeCount++;
        origWrite.apply(fs,args);
    } else {
        console.log("Delaying write:",args[0]);
        pending.push(function(){
            fs.writeFile.apply(fs,args);
        });
    }
};

有了風笛,你只需要改變

FS.readFile(filename, onRealRead);

=>

var bagpipe = new Bagpipe(10);

bagpipe.push(FS.readFile, filename, onRealRead))

風笛幫助您限制平行。 更多詳情: https : //github.com/JacksonTian/bagpipe

運行nodemon命令時遇到了同樣的問題,所以我減少了在sublime text 中打開的文件的名稱,錯誤消失了。

cwait是用於限制任何返回承諾的函數的並發執行的通用解決方案。

在您的情況下,代碼可能類似於:

var Promise = require('bluebird');
var cwait = require('cwait');

// Allow max. 10 concurrent file reads.
var queue = new cwait.TaskQueue(Promise, 10);
var read = queue.wrap(Promise.promisify(batchingReadFile));

Promise.map(files, function(filename) {
    console.log(filename);
    return(read(filename));
})

以@blak3r 的回答為基礎,這是我使用的一些速記,以防它有助於其他診斷:

如果您正在嘗試調試一個文件描述符不足的 Node.js 腳本,這里有一行給出了相關節點進程使用的lsof的輸出:

openFiles = child_process.execSync(`lsof -p ${process.pid}`);

這將同步運行由當前運行的 Node.js 進程過濾的lsof並通過緩沖區返回結果。

然后使用console.log(openFiles.toString())將緩沖區轉換為字符串並記錄結果。

使用最新的fs-extra

我在Ubuntu (16 和 18)上遇到了這個問題,有足夠的文件/套接字描述符空間(用lsof |wc -l )。 使用fs-extra版本8.1.0 更新到9.0.0 ,“錯誤:EMFILE,打開的文件太多”消失了。

我在節點處理文件系統的不同操作系統上遇到了各種問題。 文件系統顯然不是微不足道的。

對於nodemon用戶:只需使用--ignore標志即可解決問題。

例子:

nodemon app.js --ignore node_modules/ --ignore data/

我確實安裝了守望者,更改了限制等,但它在 Gulp 中不起作用。

不過,重新啟動 iterm2 實際上有幫助。

對於可能仍在尋找解決方案的任何人,使用 async-await 對我來說效果很好:

fs.readdir(<directory path></directory>, async (err, filenames) => {
    if (err) {
        console.log(err);
    }

    try {
        for (let filename of filenames) {
            const fileContent = await new Promise((resolve, reject) => {
                fs.readFile(<dirctory path + filename>, 'utf-8', (err, content) => {
                    if (err) {
                        reject(err);
                    }
                    resolve(content);
                });
            });
            ... // do things with fileContent
        }
    } catch (err) {
        console.log(err);
    }
});

這是我的兩分錢:考慮到 CSV 文件只是我已經流式傳輸數據(字符串)以避免此問題的文本行。

在我的用例中對我來說最簡單的解決方案。

它可以與優雅的 fs 或標准 fs 一起使用。 請注意,創建時文件中不會有標題。

// import graceful-fs or normal fs
const fs = require("graceful-fs"); // or use: const fs = require("fs") 

// Create output file and set it up to receive streamed data
// Flag is to say "append" so that data can be recursively added to the same file 
let fakeCSV = fs.createWriteStream("./output/document.csv", {
  flags: "a",
});

以及需要流式傳輸到文件的數據我已經這樣做了

// create custom streamer that can be invoked when needed
const customStreamer = (dataToWrite) => {
  fakeCSV.write(dataToWrite + "\n");
};

請注意,dataToWrite 只是一個帶有自定義分隔符的字符串,如“;” 要么 ”,”。 IE

const dataToWrite = "batman" + ";" + "superman"
customStreamer(dataToWrite);

這會將“batman;superman”寫入文件。


如果您正在努力部署使用 Visual Studio 模板(並具有 web.config)創建的 React 解決方案,這可能會解決您的問題。 在 Azure Release Pipelines 中,選擇模板時,請使用:

Azure 應用服務部署

代替:

將 Node.js 應用部署到 Azure 應用服務

它對我有用!

到目前為止,在任何答案中都沒有考慮或討論過另一種可能性:符號鏈接循環

Node 的遞歸文件系統觀察器似乎沒有檢測和處理符號鏈接的循環。 因此,您只需運行以下命令即可輕松地使用任意高的nfiles ulimit 觸發此錯誤:

mkdir a
mkdir a/b
cd a/b 
ln -s .. c

GNU find會注意到符號鏈接循環並中止:

$ find a -follow
a
a/b
find: File system loop detected; ‘a/b/c’ is part of the same file system loop as ‘a’.

但節點不會。 如果你在樹上設置一個監視,它會噴出一個EMFILE, too many open files錯誤。

除其他外,這可能發生在node_modules ,其中存在包含關系:

parent/
  package.json
  child/
    package.json

這就是我在嘗試構建的項目中遇到它的方式。

請注意,您不一定需要使這個問題過於復雜,再試一次就可以了。

import { promises as fs } from "fs";

const filepaths = [];
const errors = [];

function process_file(content: string) {
    // logic here
}

await Promise.all(
    filepaths.map(function read_each(filepath) {
        return fs
            .readFile(filepath, "utf8")
            .then(process_file)
            .catch(function (error) {
                if (error.code === "EMFILE") return read_each(filepath);
                else errors.push({ file: filepath, error });
            });
    }),
);

首先使用expo update更新您的 expo 版本,然后運行yarn / npm install 這為我解決了這個問題!

我遇到了這個問題,我通過運行npm update解決了它並且它起作用了。

在某些情況下,您可能需要刪除 node_modules rm -rf node_modules/

更改 Node 版本 ERR emfile too many open files 后可能會發生這種情況

  • 重新啟動計算機
  • 釀造安裝守望者

應該絕對解決這個問題

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM