Skip to content

Handling EMFILE/ENFILE on fs.open #1941

Closed
@isaacs

Description

@isaacs

For build tools that interact a lot with the file system (eg, npm, gulp, grunt, glob, and many other of the most popular node libraries, especially used by the overwhelming majority of "node devs" who are actually using it for front-end asset tooling), it is very useful to have fs.open delay when it hits the process max file limit, rather than fail to open the file. If every file being opened is guaranteed to be closed eventually, "just wait until something closes and then try again" is a pretty good strategy.

The graceful-fs module does exactly this. It is a copy of the fs module, but with the open and close methods fancied up so that it handles EMFILE gracefully.

There are three options to build such a thing. I've explored all three of them, and they have different tradeoffs. All three are being repeatedly broken by recent changes in io.js.

  1. Monkey-patch the fs module itself. This is not good because "wait on EMFILE" behavior is not something you want everywhere, so adding it globally is surprising and bad.
  2. Clone the fs module. The downside here is that it requires evaling "internal" code. Also, it means that the Stat objects aren't instanceof fs.Stat, and lots of other weird surprising edges.
  3. Write a hand-rolled artisanal copy of the fs module, re-implementing everything in core. This is tedious and wasteful, and just as prone to eventual breakage. It means keeping up with every new addition to fs, but it is technically possible.

To forestall the reader who will inevitably make this claim, I believe that "well, you shouldn't do that, and if you do, you get what you deserve" displays a lack of empathy that is incompetence bordering on malice. If npm and other cli tools stop working, io.js doesn't work, and that will limit its uptake in the real world. We are in this real world where people are doing this, so you can either say "we don't want users using our software", or we can come up with a plan to keep enabling this behavior.

At the very least, we must have a smoke test that actually runs graceful-fs's tests, as soon as possible, and reject changes that break it.

Some forward-looking options to take graceful-fs out of the equation and enable more churn in the fs module without breaking everyone:

  1. Add a flag to fs.open, fs.readFile, fs.writeFile, fs.appendFile, fs.readdir, fs.ReadStream, fs.WriteStream (and any others I'm forgetting!) to tell it to catch EMFILE errors, put them in a queue, and re-try the open operation when a fd closes. In effect, put graceful-fs in core, but make it opt-in per open.
  2. Explicitly bless either the "monkeypatch" or the "clone" approaches above, and stick to that.
  3. Vendor the fs module like we do with streams, so that graceful-fs could require it as a dependency. Then the divergence between "use this internal API for speed" vs "use the external equivalent" could be made explicit and handled with magic comments in the build process or something.
  4. Boil the ocean of migrating every module that currently uses graceful-fs to stop trying to do fs operations in parallel and handle EMFILE themselves. (This is not a realistic suggestion, imo, but included here for completeness.)

I'll be at Node Conf this week, maybe some of us could discuss this further in person.

Metadata

Metadata

Assignees

No one assigned

    Labels

    discussIssues opened for discussions and feedbacks.feature requestIssues that request new features to be added to Node.js.fsIssues and PRs related to the fs subsystem / file system.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions