web-dev-qa-db-fra.com

noeud et erreur: EMFILE, trop de fichiers ouverts

Depuis quelques jours, je cherche une solution de travail à une erreur 

Error: EMFILE, too many open files

Il semble que beaucoup de gens ont le même problème. La réponse habituelle consiste à augmenter le nombre de descripteurs de fichiers. Donc, j'ai essayé ceci: 

sysctl -w kern.maxfiles=20480

La valeur par défaut est 10240. C'est un peu étrange à mes yeux, car le nombre de fichiers que je traite dans le répertoire est inférieur à 10240. Encore plus étrange, je reçois toujours la même erreur après avoir augmenté le nombre de descripteurs de fichier .

Deuxième question: 

Après plusieurs recherches, j'ai trouvé un moyen de contourner le problème "Trop de fichiers ouverts":

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);

Malheureusement, je reçois toujours la même erreur… .. Qu'est-ce qui ne va pas avec ce code?

Une dernière question (je suis nouveau dans le javascript et le noeud), je suis en train de développer une application web Avec beaucoup de demandes pour environ 5000 utilisateurs quotidiens. J'ai plusieurs années d'expérience dans la programmation avec d'autres langages tels que Python et Java. donc à l'origine, j'ai pensé développer cette application avec Django ou un framework de jeu. Ensuite, j'ai découvert node et je dois dire que l'idée d'un modèle d'E/S non bloquant est vraiment agréable, séduisante et surtout très rapide!

Mais à quel type de problèmes dois-je m'attendre avec node? Est-ce un serveur web éprouvé en production? Quelles sont vos expériences?

132
xaverras

Car quand graceful-fs ne fonctionne pas ... ou si vous voulez simplement comprendre d'où vient la fuite. Suivez ce processus. 

(par exemple, graceful-fs ne va pas réparer votre wagon si votre problème concerne les sockets.)

Article de mon blog: http://www.blakerobertson.com/devlog/2014/1/11/how-to-determine-whats-causing-error-connect-emfile-nodejs.html

Comment isoler

Cette commande affichera le nombre de descripteurs ouverts pour les processus 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)
....

Notez que: 1023u (dernière ligne) - il s'agit du 1024ème descripteur de fichier, qui correspond au maximum par défaut.

Maintenant, regardez la dernière colonne. Cela indique quelle ressource est ouverte. Vous verrez probablement un certain nombre de lignes ayant toutes le même nom de ressource. Espérons que cela vous indique maintenant où chercher dans votre code pour la fuite.

Si vous ne connaissez pas plusieurs processus de nœud, commencez par rechercher quel processus est associé au pid 12211. Cela vous indiquera le processus.

Dans mon cas ci-dessus, j'ai remarqué qu'il y avait un tas d'adresses IP très similaires. Ils étaient tous 54.236.3.### En effectuant des recherches d'adresse IP, j'étais en mesure de déterminer dans mon cas qu'il était lié à pubnub.

Référence de commande

Utilisez cette syntaxe pour déterminer le nombre de poignées ouvertes qu'un processus a ouvertes ...

Pour obtenir un nombre de fichiers ouverts pour un certain pid

J'ai utilisé cette commande pour tester le nombre de fichiers ouverts après différents événements dans mon application.

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

Quelle est votre limite de processus?

ulimit -a

La ligne que vous voulez ressemblera à ceci: open files (-n) 1024

Changer définitivement la limite:

  • testé sur Ubuntu 14.04, nodejs v. 7.9

Si vous prévoyez d'ouvrir de nombreuses connexions (les websockets en sont un bon exemple), vous pouvez augmenter la limite de façon permanente:

  • fichier: /etc/pam.d/common-session (ajouter à la fin)} _

    session required pam_limits.so
    
  • fichier: /etc/security/limits.conf (ajoutez à la fin ou modifiez s'il existe déjà))

    root soft  nofile 40000
    root hard  nofile 100000
    
  • redémarrez votre nodejs et déconnectez-vous/connectez-vous à partir de ssh.

  • cela peut ne pas fonctionner pour les anciens NodeJS, vous devrez redémarrer le serveur
  • utilisez plutôt que si votre noeud tourne avec un UID différent.
71
blak3r

Utiliser le module graceful-fs d’Isaac Schlueter (responsable de node.js) est probablement la solution la plus appropriée. Il effectue un recul progressif si un fichier EMFILE est rencontré. Il peut être utilisé en remplacement immédiat du module fs intégré.

65
Myrne Stol

J'ai rencontré ce problème aujourd'hui et, ne trouvant pas de bonne solution, j'ai créé un module pour le résoudre. J'ai été inspiré par l'extrait de @ fbartho, mais je voulais éviter de remplacer le module fs.

Le module que j'ai écrit est Filequeue , et vous l'utilisez comme 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
        }
    });
});
6
Trey Griffith

Vous lisez trop de fichiers. Le noeud lit les fichiers de manière asynchrone, il lira tous les fichiers à la fois. Donc, vous lisez probablement la limite 10240. 

Voir si cela fonctionne:

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()
4
Tim P.

Je viens juste de finir d'écrire un petit bout de code pour résoudre ce problème moi-même. Toutes les autres solutions semblent bien trop lourdes et vous obligent à changer la structure de votre programme.

Cette solution bloque uniquement les appels fs.readFile ou fs.writeFile, de sorte qu’il n’ya plus qu’un nombre défini en vol à un moment donné. 

// 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);
        });
    }
};
2
fbartho

Avait le même problème lors de l'exécution de la commande nodemon donc j'ai réduit le nom des fichiers ouverts dans sublime text et l'erreur a disparu.

1
Buhiire Keneth

Je ne suis pas sûr que cela puisse aider quelqu'un, j'ai commencé à travailler sur un gros projet avec beaucoup de dépendances qui m'a jeté la même erreur. Mon collègue m'a suggéré d'installer watchman en utilisant brasser et cela a résolu ce problème pour moi. 

brew update
brew install watchman
1
bh4r4th

Comme nous tous, vous êtes une autre victime d'E/S asynchrones. Avec les appels asynchrones, si vous faites une boucle autour d’un grand nombre de fichiers, Node.js commencera à ouvrir un descripteur de fichier pour chaque fichier à lire, puis attendra une action jusqu’à sa fermeture.

Le descripteur de fichier reste ouvert jusqu'à ce qu'une ressource soit disponible sur votre serveur pour la lire. Même si vos fichiers sont petits et que la lecture ou la mise à jour est rapide, cela prend un certain temps, mais dans le même temps, votre boucle ne s'arrête pas pour ouvrir un nouveau descripteur de fichiers. Donc, si vous avez trop de fichiers, la limite sera bientôt atteinte et vous obtiendrez un magnifique EMFILE.

Il existe une solution: créer une file d'attente pour éviter cet effet.

Merci aux personnes qui ont écrit Async , il existe une fonction très utile pour cela. Il existe une méthode appelée Async.queue , vous créez une nouvelle file avec une limite, puis vous ajoutez des noms de fichier à la file.

Remarque: Si vous devez ouvrir plusieurs fichiers, il serait judicieux de stocker les fichiers actuellement ouverts et de ne pas les rouvrir indéfiniment.

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");
    });
}

Vous pouvez constater que chaque fichier est ajouté à la file d'attente (nom de fichier console.log), mais uniquement lorsque la file d'attente actuelle est inférieure à la limite que vous avez définie précédemment. 

async.queue obtient des informations sur la disponibilité de la file d'attente par le biais d'un rappel. Ce rappel n'est appelé que lorsque le fichier de données est lu et que toutes les actions que vous devez effectuer sont effectuées. (voir méthode fileRead)

Donc, vous ne pouvez pas être submergé par le descripteur de fichiers.

> 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
1
Plaute

Avec cornemuse, vous avez juste besoin de changement 

FS.readFile(filename, onRealRead);

=>

var bagpipe = new Bagpipe(10);

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

La cornemuse vous aide à limiter le parallèle. plus de détails: https://github.com/JacksonTian/bagpipe

1
user1837639

cwait est une solution générale pour limiter les exécutions simultanées de toutes les fonctions renvoyant des promesses.

Dans votre cas, le code pourrait être quelque chose comme:

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));
})
0
jjrv

En s'appuyant sur la réponse de @ blak3r, voici un peu de raccourci que j'utilise au cas où cela aiderait les autres à diagnostiquer:

Si vous essayez de déboguer un script Node.js qui manque de descripteurs de fichier, voici une ligne pour vous donner le résultat de lsof utilisé par le processus de noeud en question:

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

Ceci sera exécuté de manière synchrone lsof filtré par le processus Node.js en cours d’exécution et renverra les résultats via le tampon.

Puis utilisez console.log(openFiles.toString()) pour convertir le tampon en chaîne et consigner les résultats.

0
James