memfs icon indicating copy to clipboard operation
memfs copied to clipboard

Auto-closing streams throw EBADF errors when the volume is cleared in the meantime

Open falkenhawk opened this issue 5 years ago • 3 comments

Hi, I have a test which downloads a file with supertest and the file is served by setting the response body to read stream:

ctx.body = fs.createReadStream(filePath);

Then after the test finishes, I have a hook where I reset the memfs volume:

afterEach(() => {
  vol.reset();
});

It looks like when the stream is being destroyed, it asynchronously calls the method to close the file, but the volume is already reset at that point, and it throws the error:

  Error: EBADF: bad file descriptor, close
      at createError ((...)/node_modules/memfs/lib/volume.js:121:17)
      at Volume.getFileByFdOrThrow ((...)/node_modules/memfs/lib/volume.js:650:19)
      at Volume.closeSync ((...)/node_modules/memfs/lib/volume.js:878:25)
      at Immediate._onImmediate ((...)/node_modules/memfs/lib/volume.js:682:39)
      at processImmediate (internal/timers.js:456:21)

Can it be avoided somehow in this case, by adding a check somewhere along those lines if the volume was reset in the meantime? Or do you maybe have any suggestion for me to work around it?

falkenhawk avatar Jun 17 '20 09:06 falkenhawk

Could you await until the request completes in your test? So when vol.reset() is called the file is already closed.

streamich avatar Jun 17 '20 18:06 streamich

Another approach could be to not use vol.reset() but in each test create its own volume.

test('...', async () => {
  const vol = new Volume();
  // ...
});

streamich avatar Jun 17 '20 18:06 streamich

Thanks for all the suggestions,

Could you await until the request completes in your test? So when vol.reset() is called the file is already closed.

I think I am already doing it... I am not sure here, but the destroy method of the memfs' readstream might be called asynchronously, after the request (and the test) is done 🤔

// import request from supertest, set up server (koa instance) in "before" hook etc.
// ...

  it('downloads a file', async () => {
    const fileRes = await request(server)
      .get('/file/pixel.gif');

    const localFile = fs.readFileSync('./test/files/pixel.gif');
    assert.ok(localFile.equals(fileRes.body));
  });

and I prefer to use one global "volume" here, to be reused both in the test and "on the other side" of the request... 🤔

falkenhawk avatar Jun 17 '20 19:06 falkenhawk