isaacs / node-tar

tar for node

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[BUG] Pipeline stream unexpectly being closed when using `extract`

jeferson-sb opened this issue · comments

What / Why

When you attempt to extract a tarball through streams sometimes it will be closed with an error: [ERR_STREAM_PREMATURE_CLOSE]: Premature close, although the files are successfully extracted. No Errors Codes are shown in the process.

It seems to happen only on newer versions of Node > v18
Looking at the spec it seems now that streams that emit a close event before a end event trigger this error, so worth taking a look on that.

When

  • n/a

Where

  • n/a

How

Current Behavior

  • It throws an ERR_STREAM_PREMATURE_CLOSE Error when trying to extract

Steps to Reproduce

$ nvm install 18.7.0
$ npm install tar got
$ mkdir output
import stream from 'node:stream';
import { promisify } from 'util';
import tar from 'tar';
import got from 'got';

const pipeline = promisify(stream.pipeline);

pipeline(
  got.stream('https://codeload.github.com/npm/node-tar/tar.gz/main'),
  tar.extract({ cwd: './output' }, ['node-tar-main/lib'])
)

Expected Behavior

  • It should extract the content of the archive without any issues

References

  • Related to Next.js Issue #39321

does this mean instead of
https://github.com/npm/node-tar/blob/9d71c5673683d6309c75e6a85e78fa285dbe9a2d/lib/parse.js#L85-L94
you would do

    this.on('end', () => setTimeout(() => this.emit('close')))
    
    if (opt.ondone) {
      this.on(DONE, opt.ondone)
    } else {
      this.on(DONE, _ => {
        this.emit('prefinish')
        this.emit('finish')
        this.emit('end')
      })
    }

essentially? @jeferson-sb @lukekarrys

that seems on odd addition :/

@webark yeah looks like it is related to that one line

meant to say "odd" addition. I was going to try to add a test case.

I tried adding this test

t.test('ensure an open stream is not prematuraly closed', t => {
  const file = path.resolve(tars, 'body-byte-counts.tar')
  const dir = path.resolve(extractdir, 'basic-with-stream')

  t.beforeEach(async () => {
    await rimraf(dir)
    await mkdirp(dir)
  })

  const check = async t => {
    t.equal(fs.lstatSync(path.resolve(dir, '1024-bytes.txt')).size, 1024)
    await rimraf(dir)
    t.end()
  }

  t.test('async promisey', t => {
    return pipeline(
      fs.createReadStream(file),
      x({ cwd: dir }, ['1024-bytes.txt'])
    ).then(_ => check(t))
  })

  t.end()
})

But it is still passing without any modifications to the code 🤔😔 Any ideas?

I'm experiencing this.
Is there a workaround or an expected fix?

I opened up the PR #332 which fixes the issue. I'm using my fork in a project and it is working. Hopefully it will get merged and released soon.