mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
fix: hang in Deno.serveHttp() (#10923)
Waiting on next request in Deno.serveHttp() API hanged when responses were using ReadableStream. This was caused by op_http_request_next op that was never woken after response was fully written. This commit adds waker field to DenoService which is called after response is finished.
This commit is contained in:
parent
5814315b70
commit
1e1959f6fa
4 changed files with 94 additions and 11 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -1636,9 +1636,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "0.14.7"
|
version = "0.14.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e5f105c494081baa3bf9e200b279e27ec1623895cd504c7dbef8d0b080fcf54"
|
checksum = "07d6baa1b441335f3ce5098ac421fb6547c46dda735ca1bc6d0153c838f9dd83"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
|
@ -1650,7 +1650,7 @@ dependencies = [
|
||||||
"httparse",
|
"httparse",
|
||||||
"httpdate",
|
"httpdate",
|
||||||
"itoa",
|
"itoa",
|
||||||
"pin-project",
|
"pin-project-lite",
|
||||||
"socket2 0.4.0",
|
"socket2 0.4.0",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
|
import { chunkedBodyReader } from "../../../test_util/std/http/_io.ts";
|
||||||
|
import { BufReader, BufWriter } from "../../../test_util/std/io/bufio.ts";
|
||||||
|
import { Buffer } from "../../../test_util/std/io/buffer.ts";
|
||||||
|
import { TextProtoReader } from "../../../test_util/std/textproto/mod.ts";
|
||||||
import {
|
import {
|
||||||
assert,
|
assert,
|
||||||
assertEquals,
|
assertEquals,
|
||||||
|
@ -6,6 +10,33 @@ import {
|
||||||
unitTest,
|
unitTest,
|
||||||
} from "./test_util.ts";
|
} from "./test_util.ts";
|
||||||
|
|
||||||
|
async function writeRequestAndReadResponse(conn: Deno.Conn): Promise<string> {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
|
const w = new BufWriter(conn);
|
||||||
|
const r = new BufReader(conn);
|
||||||
|
const body = `GET / HTTP/1.1\r\nHost: 127.0.0.1:4501\r\n\r\n`;
|
||||||
|
const writeResult = await w.write(encoder.encode(body));
|
||||||
|
assertEquals(body.length, writeResult);
|
||||||
|
await w.flush();
|
||||||
|
const tpr = new TextProtoReader(r);
|
||||||
|
const statusLine = await tpr.readLine();
|
||||||
|
assert(statusLine !== null);
|
||||||
|
const headers = await tpr.readMIMEHeader();
|
||||||
|
assert(headers !== null);
|
||||||
|
|
||||||
|
const chunkedReader = chunkedBodyReader(headers, r);
|
||||||
|
const buf = new Uint8Array(5);
|
||||||
|
const dest = new Buffer();
|
||||||
|
let result: number | null;
|
||||||
|
while ((result = await chunkedReader.read(buf)) !== null) {
|
||||||
|
const len = Math.min(buf.byteLength, result);
|
||||||
|
await dest.write(buf.subarray(0, len));
|
||||||
|
}
|
||||||
|
return decoder.decode(dest.bytes());
|
||||||
|
}
|
||||||
|
|
||||||
unitTest({ perms: { net: true } }, async function httpServerBasic() {
|
unitTest({ perms: { net: true } }, async function httpServerBasic() {
|
||||||
const promise = (async () => {
|
const promise = (async () => {
|
||||||
const listener = Deno.listen({ port: 4501 });
|
const listener = Deno.listen({ port: 4501 });
|
||||||
|
@ -373,3 +404,49 @@ unitTest(
|
||||||
await delay(300);
|
await delay(300);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
unitTest(
|
||||||
|
{ perms: { net: true } },
|
||||||
|
// Issue: https://github.com/denoland/deno/issues/10870
|
||||||
|
async function httpServerHang() {
|
||||||
|
// Quick and dirty way to make a readable stream from a string. Alternatively,
|
||||||
|
// `readableStreamFromReader(file)` could be used.
|
||||||
|
function stream(s: string): ReadableStream<Uint8Array> {
|
||||||
|
return new Response(s).body!;
|
||||||
|
}
|
||||||
|
|
||||||
|
const httpConns: Deno.HttpConn[] = [];
|
||||||
|
const promise = (async () => {
|
||||||
|
let count = 0;
|
||||||
|
const listener = Deno.listen({ port: 4501 });
|
||||||
|
for await (const conn of listener) {
|
||||||
|
(async () => {
|
||||||
|
const httpConn = Deno.serveHttp(conn);
|
||||||
|
httpConns.push(httpConn);
|
||||||
|
for await (const { respondWith } of httpConn) {
|
||||||
|
respondWith(new Response(stream("hello")));
|
||||||
|
|
||||||
|
count++;
|
||||||
|
if (count >= 2) {
|
||||||
|
listener.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const clientConn = await Deno.connect({ port: 4501 });
|
||||||
|
|
||||||
|
const r1 = await writeRequestAndReadResponse(clientConn);
|
||||||
|
assertEquals(r1, "hello");
|
||||||
|
|
||||||
|
const r2 = await writeRequestAndReadResponse(clientConn);
|
||||||
|
assertEquals(r2, "hello");
|
||||||
|
|
||||||
|
clientConn.close();
|
||||||
|
await promise;
|
||||||
|
for (const conn of httpConns) {
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
|
@ -55,7 +55,7 @@ dlopen = "0.1.8"
|
||||||
encoding_rs = "0.8.28"
|
encoding_rs = "0.8.28"
|
||||||
filetime = "0.2.14"
|
filetime = "0.2.14"
|
||||||
http = "0.2.3"
|
http = "0.2.3"
|
||||||
hyper = { version = "0.14.5", features = ["server", "stream", "http1", "http2", "runtime"] }
|
hyper = { version = "0.14.9", features = ["server", "stream", "http1", "http2", "runtime"] }
|
||||||
indexmap = "1.6.2"
|
indexmap = "1.6.2"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
libc = "0.2.93"
|
libc = "0.2.93"
|
||||||
|
|
|
@ -66,6 +66,7 @@ struct ServiceInner {
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
struct Service {
|
struct Service {
|
||||||
inner: Rc<RefCell<Option<ServiceInner>>>,
|
inner: Rc<RefCell<Option<ServiceInner>>>,
|
||||||
|
waker: Rc<deno_core::futures::task::AtomicWaker>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HyperService<Request<Body>> for Service {
|
impl HyperService<Request<Body>> for Service {
|
||||||
|
@ -160,15 +161,16 @@ async fn op_http_request_next(
|
||||||
let cancel = RcRef::map(conn_resource.clone(), |r| &r.cancel);
|
let cancel = RcRef::map(conn_resource.clone(), |r| &r.cancel);
|
||||||
|
|
||||||
poll_fn(|cx| {
|
poll_fn(|cx| {
|
||||||
|
conn_resource.deno_service.waker.register(cx.waker());
|
||||||
let connection_closed = match conn_resource.poll(cx) {
|
let connection_closed = match conn_resource.poll(cx) {
|
||||||
Poll::Pending => false,
|
Poll::Pending => false,
|
||||||
Poll::Ready(Ok(())) => {
|
Poll::Ready(Ok(())) => {
|
||||||
// close ConnResource
|
// try to close ConnResource, but don't unwrap as it might
|
||||||
state
|
// already be closed
|
||||||
|
let _ = state
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.resource_table
|
.resource_table
|
||||||
.take::<ConnResource>(conn_rid)
|
.take::<ConnResource>(conn_rid);
|
||||||
.unwrap();
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Poll::Ready(Err(e)) => {
|
Poll::Ready(Err(e)) => {
|
||||||
|
@ -188,7 +190,6 @@ async fn op_http_request_next(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(request_resource) =
|
if let Some(request_resource) =
|
||||||
conn_resource.deno_service.inner.borrow_mut().take()
|
conn_resource.deno_service.inner.borrow_mut().take()
|
||||||
{
|
{
|
||||||
|
@ -409,6 +410,9 @@ async fn op_http_response(
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if maybe_response_body_rid.is_none() {
|
||||||
|
conn_resource.deno_service.waker.wake();
|
||||||
|
}
|
||||||
Ok(maybe_response_body_rid)
|
Ok(maybe_response_body_rid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,11 +434,13 @@ async fn op_http_response_close(
|
||||||
.ok_or_else(bad_resource_id)?;
|
.ok_or_else(bad_resource_id)?;
|
||||||
drop(resource);
|
drop(resource);
|
||||||
|
|
||||||
poll_fn(|cx| match conn_resource.poll(cx) {
|
let r = poll_fn(|cx| match conn_resource.poll(cx) {
|
||||||
Poll::Ready(x) => Poll::Ready(x),
|
Poll::Ready(x) => Poll::Ready(x),
|
||||||
Poll::Pending => Poll::Ready(Ok(())),
|
Poll::Pending => Poll::Ready(Ok(())),
|
||||||
})
|
})
|
||||||
.await
|
.await;
|
||||||
|
conn_resource.deno_service.waker.wake();
|
||||||
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn op_http_request_read(
|
async fn op_http_request_read(
|
||||||
|
|
Loading…
Reference in a new issue