Solving the "EMFILE, too many open files" problem

  • Tutorial
Good afternoon.
Putting 404 error messages into a separate log file is such a common thing that it would seem that there can be no difficulties with this. At least, I thought so, until in one second the client requested one and a half thousand missing files.
The Node.js server cursed "EMFILE, too many open files" and disconnected.
(In debug mode, I specifically do not catch errors that fall into the main loop)

So, what was the save function to the file:
        log: function (filename, text) {
// запись в конец файла filename строки now() + text
            var s = utils.digitime() + ' ' + text + '\n';
// utils.digitime() - возвращает текущую дату-время в виде строки в формате ДД.ММ.ГГГГ чч:мм:сс
            fs.open(LOG_PATH + filename, "a", 0x1a4, function (error, file_handle) {
                if (!error) {
                    fs.write(file_handle, s, null, 'utf8', function (err) {
                        if (err) {
                            console.log(ERR_UTILS_FILE_WRITE + filename + ' ' + err);
                        }
                        fs.close(file_handle, function () {
                            callback();
                        });
                    });
                }
                else {
                    console.log(ERR_UTILS_FILE_OPEN + filename + ' ' + error);
                    callback();
                }
            });
        }

Well, that is, everything is directly "on the forehead" - open, write down, if there are any errors - display on the console. However, as mentioned above, if you call it too often, the files simply do not have time to close. In Linux, for example, we run into the value of kern.maxfiles with all the unpleasant consequences.

The most interesting

For the solution, I chose the async library, without which I can not imagine life.
The log function itself was moved to the “private” scope of the module, renamed to __log and slightly modified: now it has a callback:
__log = function (filename, text) {
        return function (callback) {
            var s = utils.digitime() + ' ' + text + '\n';
            fs.open(LOG_PATH + filename, "a", 0x1a4, function (error, file_handle) {
                if (!error) {
                    fs.write(file_handle, s, null, 'utf8', function (err) {
                        if (err) {
                            console.log(ERR_UTILS_FILE_WRITE + filename + ' ' + err);
                        }
                        fs.close(file_handle, function () {
                            callback();
                        });                        
                    });
                }
                else {
                    console.log(ERR_UTILS_FILE_OPEN + filename + ' ' + error);
                    callback();
                }
            });
        };
    };


The most important thing: in private we create the __writeQueue variable:
    __writeQueue = async.queue(function (task, callback) {
        task(callback);
    }, MAX_OPEN_FILES);


and in the public part of the module, log now looks quite simple:
        log: function (filename, text) {
            __writeQueue.push(__log(filename, text));
        },

Is that all ?!

Exactly. Other modules still call this f-ju something like
function errorNotFound (req, res) {
    utils.log(LOG_404, '' + req.method + '\t' + req.url + '\t(' + (accepts) + ')\t requested from ' + utils.getClientAddress(req));
..

and no mistakes.

The mechanism is simple: we set the MAX_OPEN_FILES constant to a reasonable number, less than the maximum allowable number of open file descriptors (for example, 256). Further, all recording attempts will be parallelized, but only until their number reaches the specified limit. All freshly arriving will be queued and started only after the completion of previous attempts (remember, we added callback? Just for this!).

I hope this article will help solve this problem for those who are faced with it. Or - even better - will serve as a preventative measure.
Good luck.

Also popular now: