Skip to content

WritableStream leaks a descriptor if it is not ended #40053

@mmomtchev

Description

@mmomtchev

Version

12-14-16

Platform

All

Subsystem

Streams

What steps will reproduce the bug?

Consider this code:

const axios = require('axios');
const fs = require('fs');
const stream = require('stream');
const { promisify } = require('util');
const streamFinished = promisify(stream.finished);

const dl = axios.create({
	responseType: 'stream'
});

(async () => {
	do {
		console.log('iteration');
		try {
			const writer = fs.createWriteStream('temp');
			const reader = await dl.get('https://www.google.com/give_me_404');
			reader.data.pipe(writer);
			await streamFinished(writer);
		} catch (e) {
			console.log(e.message);
		}
	} while (true);
})();

It will leak a descriptor at every iteration. This has nothing to do with axios - in fact this is enough to leak:

const fs = require('fs');

do {
	console.log('iteration');
	const writer = fs.createWriteStream('temp');
} while (true);

How often does it reproduce? Is there a required condition?

Always

What is the expected behavior?

Destruction of WritableStream that is not piped (by going out of scope) should free the file descriptor

What do you see instead?

The problem is that in the first example the only way to not leak a descriptor is:

const axios = require('axios');
const fs = require('fs');
const stream = require('stream');
const { promisify } = require('util');
const streamFinished = promisify(stream.finished);

const dl = axios.create({
	responseType: 'stream'
});

(async () => {
	do {
		console.log('iteration');
                let writer, reader;
		try {
			writer = fs.createWriteStream('temp');
			reader = await dl.get('https://www.google.com/give_me_404');
			reader.data.pipe(writer);
			await streamFinished(writer);
		} catch (e) {
                        try {
                             reader.close();
                        } catch () {}
                        try {
                             writer.close();
                        } catch () {}
			console.log(e.message);
		}
	} while (true);
})();

This is the only way to never leak a descriptor and to handle all type of errors that can happen.

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    streamIssues and PRs related to the stream subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions