Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server side aborted request in stream mode hang with node 14.3.0 #1295

Closed
2 tasks done
darksabrefr opened this issue May 28, 2020 · 7 comments · Fixed by #1296
Closed
2 tasks done

Server side aborted request in stream mode hang with node 14.3.0 #1295

darksabrefr opened this issue May 28, 2020 · 7 comments · Fixed by #1296
Labels
bug Something does not work as it should ✭ help wanted ✭

Comments

@darksabrefr
Copy link

Describe the bug

  • Node.js version: 14.3.0
  • OS & version: Ubuntu 19

Server side aborted requests in stream mode conduce got to hang with node >= 14.3.0. In fact the aborted event from the IncomingMessage is not transmitted or transformed to an error in the stream got returns.

Code to reproduce

const http = require('http');
const got = require('got');

// http server that aborts a request
const server = http.createServer((req, res) => {
	res.on('close', () => {
		console.log('server close')
	})

	res.writeHead(200, { 'Content-Type': 'text/plain' });
	res.write('chunk 1');

	setTimeout(() => res.write('chunk 2'), 1000);
	setTimeout(() => res.destroy(), 2000);
});
server.listen(8000);

// got request
(async () => {
	try {
		const gotOptions = {timeout: {socket: 2000, request: 3000}, isStream: true};
		const httpRequest = got('http://localhost:8000', gotOptions);
		for await (const chunk of httpRequest) {
			console.log(`client data: aborted=${httpRequest.aborted}, data=${chunk.toString()}`)
		}
		console.log(`client stream end`);
	} catch (e) {
		console.log(`client error: error=${e}`);
	}
	console.log(`end`);
})();

Node output

14.3.0
client data: aborted=false, data=chunk 1
client data: aborted=false, data=chunk 2
server close
14.2.0
client data: aborted=false, data=chunk 1
client data: aborted=false, data=chunk 2
client error: error=ReadError: The server aborted the pending request
end
server close

Comments

With node 14.2.0 we can see when the request is aborted that an error in thrown from the for await loop. We can see the end message too signaling the request is terminated. With node 14.3.0, no error is thrown and the stream is not terminated, we are stuck in the for await loop

Workaround

It's possible to use this workaround to restore the previous behavior (but we get a got.RequestError and not more a got.ReadError):

const {once} = require('events');
const got = require('got');

(async () => {
	try {
		const gotOptions = {timeout: 3000, isStream: true};
		const httpRequest = got('http://localhost:8000', gotOptions);
		const [httpResponse] = await once(httpRequest, 'response');
		httpResponse.on('aborted', () => {
			httpRequest.destroy(new Error('The server aborted the pending request'));
		});
		for await (const chunk of httpRequest) {
			console.log(`client data: aborted=${httpRequest.aborted}, data=${chunk.toString()}`)
		}
		console.log(`client stream end`);
	} catch (e) {
		console.log(`client error: error=${e}`);
	}
	console.log(`end`);
})();

Checklist

  • I have read the documentation.
  • I have tried my code with the latest version of Node.js and Got.
@szmarczak szmarczak added bug Something does not work as it should ✭ help wanted ✭ labels May 28, 2020
@Giotino
Copy link
Collaborator

Giotino commented May 29, 2020

This is causing the test "throws an error if the server aborted the request" to fail.

These Node.JS commits (merged into 14.3.0) could be relevant to this issue:
nodejs/node@b634d4b
nodejs/node@cc5c8e0

@Giotino
Copy link
Collaborator

Giotino commented May 29, 2020

The problem seems to be with the aborted property of the got request:
https://github.com/sindresorhus/got/blob/master/source/core/index.ts#L1618,L1620

get aborted(): boolean {
	return (this[kRequest]?.destroyed ?? this.destroyed) && !(this[kOriginalResponse]?.complete);
}

or, at least, the way it's used here:
https://github.com/sindresorhus/got/blob/master/source/core/index.ts#L1059,L1062

response.once('aborted', () => {
	if (this.aborted) {
		return;
	}

@Giotino
Copy link
Collaborator

Giotino commented May 29, 2020

@szmarczak

TODO: Remove the next if when these get fixed:
nodejs/node#32851

has been fixed in 14.3.0

@szmarczak
Copy link
Collaborator

But it shouldn't be breaking imo

@szmarczak
Copy link
Collaborator

What's the response.complete value on the aborted event (wait 0.1s before logging)?

@Giotino
Copy link
Collaborator

Giotino commented May 29, 2020

What's the response.complete value on the aborted event (wait 0.1s before logging)?

reponse.complete is false.

But there's a difference here: this[kRequest]?.destroyed

Node.JS 14.2.0: false
Node.JS 14.3.0: true (so this.aborted is true)

@darksabrefr
Copy link
Author

I can confirm the fix is functional. Many thanks to @Giotino & @szmarczak !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something does not work as it should ✭ help wanted ✭
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants