-
Notifications
You must be signed in to change notification settings - Fork 5.4k
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
Process exits even when catching: Uncaught (in promise) Http: connection closed before message completed #11595
Comments
I have encountered the same problem on 1.12.2, and the error remains on 1.13.0 but I can catch it though. This is my code: const server = Deno.listen({ port: 80 });
for await (const conn of server) {
handle(conn);
}
async function handle(conn: Deno.Conn) {
const httpConn = Deno.serveHttp(conn);
for await (const requestEvent of httpConn) {
try {
await requestEvent.respondWith(new Response(
"Hello World!", {
status: 200,
headers: {
"content-type": "text/html",
},
},
),
);
} catch (e) {
console.log(e);
}
}
} Terminal will output: Http: connection closed before message completed
at deno:core/01_core.js:106:46
at unwrapOpResult (deno:core/01_core.js:126:13)
at async Object.respondWith (deno:extensions/http/01_http.js:183:27)
at async handle I don't get this error when quickly refreshing localhost in a browser but when I leave the server idle for a bit and refresh after a longer interval (~20-30 seconds) |
Frustrating - still happens on 1.13.0 for me when refreshing too fast from the browser.
My server loop looks identical to yours @GJZwiers, except my response was const res = await computeResponse();
await requestEvent.respondWith(res); No matter where I place the try/catch, the process exits! Possibly interesting, removing the const res = await computeResponse();
requestEvent.respondWith(res).catch(e => console.log(`Error in respondWith`, e)); yields:
The Deno 1.13 release notes mention that this api has been battle tested: @lucacasonato, @bartlomieju, @kitsonk, could you provide the battle-tested http server loop you've been using? Any hints on how to prevent these errors from automatically causing process exits? Happy to debug more if shown where to look, still pretty new to Deno Thanks! |
Thanks for reports @johnspurlock @GJZwiers, I saw this issue today and it is something we'll look into this week to release fixes for 1.13.1 on Monday. I'll get back to you once I debug the problem, but from the initial investigation it seems to be an error that should be handled internally - looks like client is closing the connection before server writes full response. |
So I'm using following script for testing: const server = Deno.listen({ port: 8080 });
for await (const conn of server) {
handle(conn);
}
async function handle(conn: Deno.Conn) {
const httpConn = Deno.serveHttp(conn);
for await (const requestEvent of httpConn) {
requestEvent.respondWith(
new Response(
"Hello World!",
{
status: 200,
headers: {
"content-type": "text/html",
},
},
),
).catch((e) => {
console.error("uncaught in respondwith", e);
});
}
} I've been able to trigger this error only 2 or 3 times when using the browser, but I'm able to trigger it more or less consistently using |
Thanks to @asos-craigmorten for coming up with a test case, I added it in #11717, but again - error is properly caught and the there's no uncaught promise that causes the process to exit. I tested it against Deno 1.12.2 and 1.13. @johnspurlock @GJZwiers is there any more code that you could share that could help us pinpoint the problem? |
It's trivial to reproduce on my side on one of my projects, but the project is fairly complex. I'll work on coming up with a sharable-but-still-reproducible form of it later today (GMT-5) In case it helps, the initial request loads among other things a webapp in debug mode, which in turn makes a flurry of additional requests back to the same server. So "refreshing too quickly" in the browser terminates possibly many simultaneous inflight requests at once. |
Thanks that's a good pointer. I'll try to come up with a test case that does the same. |
I've been able to reproduce the error consistently by doing the following in a browser (Firefox):
If I keep the server running and refresh it seems to happen very irregularly, I've basically let the server idle for a while on localhost and then refreshing, but it doesn't show any pattern so far. Tested with both Ubuntu WSL 2 terminal and PowerShell |
Unfortunately I'm unable to reproduce the error with those step. Could you folks try with latest Deno canary ( |
So this doesn't seem to work when I have a firefox console/debugger open at the same time :/ On the canary I still get the error if I open a single new firefox instance, open localhost:8080 before running the server and then refresh once it's up. But connecting to it from Deno with a |
Still able to reproduce the process exit on canary
os: I ensured my server loop is character for character the same as yours, @bartlomieju The only differences are that I destructure the requestEvent, I have one line that computes the response via const { request, respondWith } = requestEvent;
const res = await localRequestServer.fetch(request);
respondWith(res).catch((e) => { console.error(`uncaught in respondwith ${request.url}`, e); }); Run command:
Browser command-r (refresh) quickly on brave/chrome/safari/ff on mac yields:
I'll start working on narrowing my server down to something shareable |
@johnspurlock I used this code: const server = Deno.listen({ port: 8080 });
const localRequestServer = {
fetch: async (_req) => {
await Promise.resolve();
return new Response(
"Hello World!",
{
status: 200,
headers: {
"content-type": "text/html",
},
},
)
},
};
for await (const conn of server) {
handle(conn);
}
async function handle(conn: Deno.Conn) {
const httpConn = Deno.serveHttp(conn);
for await (const requestEvent of httpConn) {
const { request, respondWith } = requestEvent;
const res = await localRequestServer.fetch(request);
respondWith(res).catch((e) => { console.error(`uncaught in respondwith ${request.url}`, e); });
}
} with
(I use I'm refreshing browser tab multiple times, or even holding Cmd + R to trigger a lot of "canceled" requests and I can't even trigger the same error. In the test cases I wrote the error can be triggered but it's still properly handled. I'm afraid without full reproduction code including you Aslo, are you running on Mac M1? |
Nope, MacBook Pro (16-inch, 2019) 2.4 GHz 8-Core Intel Core i9 Working right now on getting a smaller shareable version of my server, hard because it's rendered using data from services needing account secrets etc. Another possible culprit is that my root resource (the url that gets reloaded by the user) Response body is a |
Repros trivially when the webapp is completely disabled, so this does not seem to be the culprit. |
Alright, this should demonstrate the issue in a minimal way. function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function computeResponse(): Promise<Response> {
await sleep(200);
const { readable, writable } = new TransformStream<Uint8Array>();
const writer = writable.getWriter();
try {
writer.write(new TextEncoder().encode('written to the writable side of a TransformStream'));
} catch (e) {
console.log(`Error in writer.write`, e);
}
writer.close();
return new Response(readable);
}
async function handle(conn: Deno.Conn) {
const httpConn = Deno.serveHttp(conn);
for await (const { request, respondWith } of httpConn) {
const res = await computeResponse();
respondWith(res).catch((e) => { console.error(`uncaught in respondWith ${request.url}`, e); });
}
}
const server = Deno.listen({ port: 3002 });
for await (const conn of server) {
handle(conn);
} Refreshing the site slowly in a browser will work, but once you refresh too quickly, or hold down command-r, the process will crash no matter where you place a try/catch. Depending on how fast your machine is, you may need to increase the sleep ms a bit, but this is set pretty high already and reproduces easily on my machine. |
Thanks @johnspurlock I'm getting the panic now. I will try to fix it for tomorrow. |
@johnspurlock so with help from @lucacasonato and @ry we've debugged the problem. Firstly, there's a problem with stack trace that doesn't give useful information, the best I could get was:
which points that there's unresolved promise in the There's also problem in your code in that you don't await If you rewrite your code like so: async function writeResponse(writer): Promise<Response> {
try {
await writer.write(new TextEncoder().encode('written to the writable side of a TransformStream'));
} catch (e) {
console.log(`Error in writer.write`, e);
}
await writer.close();
}
async function handle(conn: Deno.Conn) {
const httpConn = Deno.serveHttp(conn);
for await (const { request, respondWith } of httpConn) {
const { readable, writable } = new TransformStream<Uint8Array>();
const writer = writable.getWriter();
writeResponse(writer).catch((e) => { console.error(`uncaught in writeResponse ${request.url}`, e); });
const res = new Response(readable);
const res = await computeResponse();
respondWith(res).catch((e) => { console.error(`uncaught in respondWith ${request.url}`, e); });
}
}
const server = Deno.listen({ port: 3002 });
for await (const conn of server) {
handle(conn);
} the problem goes away. (Writing of response needs to be done in an async IIFE because of WHATWG streams backpressure). There's also a bug in internal code that doesn't properly clean up one resource that I'm working on a fix for, Thanks for providing this test case - this is quite an esoteric bug but I'm glad we got to the bottom of it. |
What resource is this @bartlomieju? I've noticed that if code throws prior to calling |
Yes, it's the same one, it's actually called |
Thanks for looking into this. Unfortunately in my case I can't change that function - I'm writing a framework, and my sample was simplified, but imagine that
|
Yes, I agree the situation is very unfortunate and there is some problem with stack traces, but I'm afraid we're constrained to what V8 gives us. There's #7644 that could alleviate the problem, but the V8 APIs are missing.
Thanks a good idea. |
If I reload the browser before the response from the server arrives I get the error too, how can I catch this error so that the application does not crash? Example: const sleep = (ms: number) => {
return new Promise(resolve => setTimeout(resolve, ms))
}
const server = Deno.listen({ port: 8000 });
console.log("http://localhost:8000/");
try {
for await (const conn of server) {
(async () => {
const httpConn = Deno.serveHttp(conn);
for await (const { respondWith } of httpConn) {
// Placeholder for some calculations or requests from other services
// This makes the difference
await sleep(200);
respondWith(new Response("hello world", {
status: 200,
}));
}
})();
}
} catch (error) {
// Note: This is not called for the error: "Http: connection closed before message completed"
console.warn("Error", error);
} Start this with If I remove |
Seems to be fixed in the new HTTP server API: denoland/std#1128 Example import { serveListener, Handler } from "https://deno.land/std/http/server.ts";
const sleep = (ms: number) => {
return new Promise(resolve => setTimeout(resolve, ms))
}
const requestHandler: Handler = async (/*request, connInfo*/) => {
await sleep(200);
const resp = new Response("hello world");
return resp;
}
const listener = Deno.listen({ port: 8000 });
serveListener(listener, requestHandler); |
@JumpLink I think a new issue should be created |
deno 1.32.3 (release, x86_64-unknown-linux-gnu) error: Uncaught (in promise) Http: connection closed before message completed Not doing anything special. Basically using the code from the example page: https://deno.land/[email protected]/examples/http_server I can definitely say that this issue is random. This happened just now after starting my server and sending 2 requests. |
I'm experiencing the same thing on 1.36.4. |
deno 1.12.2 (release, x86_64-apple-darwin)
v8 9.2.230.14
typescript 4.3.5
Refreshing the browser quickly multiple times against a simple local http server can produce a situation where a promise is rejected that cannot be caught, and causes process exits. This is not good when trying to write a stable http server.
yields:
Tried a
.catch()
, now none of my code appears in this stacktrace at all!yields:
I'm at a loss for what to do here, especially since unhandled promise rejections cannot currently be caught globally (#7013)
Found older #10128 and #10380, but the code seems to have substantially changed since then, based on the stacktraces.
The text was updated successfully, but these errors were encountered: