Skip to content

Commit 0b3c80c

Browse files
addaleaxtargos
authored andcommitted
http2: fix issues with aborted respondWithFile()s
PR-URL: #21561 Fixes: #20824 Fixes: #21560 Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Ujjwal Sharma <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent 6bb2b5a commit 0b3c80c

File tree

4 files changed

+52
-9
lines changed

4 files changed

+52
-9
lines changed

src/node_http2.cc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2060,10 +2060,9 @@ int Http2Stream::DoWrite(WriteWrap* req_wrap,
20602060
uv_buf_t* bufs,
20612061
size_t nbufs,
20622062
uv_stream_t* send_handle) {
2063-
CHECK(!this->IsDestroyed());
20642063
CHECK_NULL(send_handle);
20652064
Http2Scope h2scope(this);
2066-
if (!IsWritable()) {
2065+
if (!IsWritable() || IsDestroyed()) {
20672066
req_wrap->Done(UV_EOF);
20682067
return 0;
20692068
}

src/stream_pipe.cc

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,11 @@ void StreamPipe::Unpipe() {
5757
if (is_closed_)
5858
return;
5959

60-
// Note that we cannot use virtual methods on `source` and `sink` here,
61-
// because this function can be called from their destructors via
60+
// Note that we possibly cannot use virtual methods on `source` and `sink`
61+
// here, because this function can be called from their destructors via
6262
// `OnStreamDestroy()`.
63+
if (!source_destroyed_)
64+
source()->ReadStop();
6365

6466
is_closed_ = true;
6567
is_reading_ = false;
@@ -144,7 +146,8 @@ void StreamPipe::ProcessData(size_t nread, const uv_buf_t& buf) {
144146
is_writing_ = true;
145147
is_reading_ = false;
146148
res.wrap->SetAllocatedStorage(buf.base, buf.len);
147-
source()->ReadStop();
149+
if (source() != nullptr)
150+
source()->ReadStop();
148151
}
149152
}
150153

@@ -183,13 +186,15 @@ void StreamPipe::WritableListener::OnStreamAfterShutdown(ShutdownWrap* w,
183186

184187
void StreamPipe::ReadableListener::OnStreamDestroy() {
185188
StreamPipe* pipe = ContainerOf(&StreamPipe::readable_listener_, this);
189+
pipe->source_destroyed_ = true;
186190
if (!pipe->is_eof_) {
187191
OnStreamRead(UV_EPIPE, uv_buf_init(nullptr, 0));
188192
}
189193
}
190194

191195
void StreamPipe::WritableListener::OnStreamDestroy() {
192196
StreamPipe* pipe = ContainerOf(&StreamPipe::writable_listener_, this);
197+
pipe->sink_destroyed_ = true;
193198
pipe->is_eof_ = true;
194199
pipe->Unpipe();
195200
}

src/stream_pipe.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,18 @@ class StreamPipe : public AsyncWrap {
2323
}
2424

2525
private:
26-
StreamBase* source();
27-
StreamBase* sink();
26+
inline StreamBase* source();
27+
inline StreamBase* sink();
2828

29-
void ShutdownWritable();
30-
void FlushToWritable();
29+
inline void ShutdownWritable();
30+
inline void FlushToWritable();
3131

3232
bool is_reading_ = false;
3333
bool is_writing_ = false;
3434
bool is_eof_ = false;
3535
bool is_closed_ = true;
36+
bool sink_destroyed_ = false;
37+
bool source_destroyed_ = false;
3638

3739
// Set a default value so that when we’re coming from Start(), we know
3840
// that we don’t want to read just yet.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
const http2 = require('http2');
7+
const net = require('net');
8+
9+
const {
10+
HTTP2_HEADER_CONTENT_TYPE
11+
} = http2.constants;
12+
13+
const server = http2.createServer();
14+
server.on('stream', common.mustCall((stream) => {
15+
stream.respondWithFile(process.execPath, {
16+
[HTTP2_HEADER_CONTENT_TYPE]: 'application/octet-stream'
17+
});
18+
}));
19+
20+
server.listen(0, common.mustCall(() => {
21+
const client = http2.connect(`http://localhost:${server.address().port}`);
22+
const req = client.request();
23+
24+
req.on('response', common.mustCall(() => {}));
25+
req.on('data', common.mustCall(() => {
26+
net.Socket.prototype.destroy.call(client.socket);
27+
server.close();
28+
}));
29+
req.end();
30+
}));
31+
32+
// TODO(addaleax): This is a *hack*. HTTP/2 needs to have a proper way of
33+
// dealing with this kind of issue.
34+
process.once('uncaughtException', (err) => {
35+
if (err.code === 'ECONNRESET') return;
36+
throw err;
37+
});

0 commit comments

Comments
 (0)