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

Inconsistent Handling of TCP Stream Closure Causes BadResource Error in Deno #27133

Open
PutziSan opened this issue Nov 28, 2024 · 1 comment · May be fixed by #27463
Open

Inconsistent Handling of TCP Stream Closure Causes BadResource Error in Deno #27133

PutziSan opened this issue Nov 28, 2024 · 1 comment · May be fixed by #27463
Assignees
Labels
ext/net related to ext/net needs investigation requires further investigation before determining if it is an issue or not triage required 👀 Deno team needs to make a decision if this change is desired

Comments

@PutziSan
Copy link

Version: deno 2.1.1 (stable, release, x86_64-unknown-linux-gnu)

BadResource Error with Deno's TCP Stream Handling

Deno throws a BadResource: Bad resource ID error with an unhelpful stack trace that does not point to the user's code. The error occurs when a TCP connection is closed before the request body is fully read. It depends on request size and the execution context:

  • When server and client run in the same script: The error occurs consistently when the request size exceeds 512 bytes.
  • When server and client are in separate scripts: The server can handle larger requests but still eventually throws the same error.

Steps to Reproduce

  1. Create a Deno server that reads the request body in chunks using ReadableStream.getReader().
  2. Send a POST request with a total size exceeding 512 bytes.
  3. Close the connection abruptly from the client after sending the request.

Code to Reproduce

// Server
Deno.serve(async (req) => {
  console.log("Request received");

  const reader = req.body!.getReader();
  const decoder = new TextDecoder();
  let body = "";

  try {
    while (true) {
      console.log("Reading next chunk");
      const { done, value } = await reader.read();
      if (done) break;
      body += decoder.decode(value, { stream: true });
    }
  } catch (e) {
    console.error("Unexpected Error:", e);
    Deno.exit(0);
  }

  throw new Error("This should not be reached");
});

// Client
const REQUEST_SIZE = 513;
const contentLength = REQUEST_SIZE - 59 - REQUEST_SIZE.toString().length;
const body = "a".repeat(contentLength);
const request =
  `POST / HTTP/1.1\r\n` +
  `Host: 127.0.0.1:8000\r\n` +
  `Content-Length: ${contentLength}\r\n` +
  "\r\n" +
  body;

const connection = await Deno.connect({ hostname: "127.0.0.1", port: 8000 });
await connection.write(new TextEncoder().encode(request));
connection.close();

Observed Error

When the above code is executed (in a single script), the server logs the initial chunks but eventually throws the following error:

Unexpected Error: BadResource: Bad resource ID
    at Object.pull (ext:deno_web/06_streams.js:937:38)
    at Module.invokeCallbackFunction (ext:deno_webidl/00_webidl.js:1105:16)
    at ReadableByteStreamController.pullAlgorithm (ext:deno_web/06_streams.js:3558:14)
    at readableByteStreamControllerCallPullIfNeeded (ext:deno_web/06_streams.js:1233:49)
    at ext:deno_web/06_streams.js:1241:11
    at eventLoopTick (ext:core/01_core.js:175:7) {
  name: "BadResource"
}

Expected Behavior

  1. The stack trace should point to the user's code causing the error.
  2. The server should handle TCP connection closures consistently, irrespective of the request size.
  3. The error message should be more meaningful.

Additional Information

I encountered this issue while investigating a related problem with Request.text() in Deno. I have filed a separate issue for that. These two issues may be connected. See #27132

@bartlomieju bartlomieju added needs investigation requires further investigation before determining if it is an issue or not ext/net related to ext/net triage required 👀 Deno team needs to make a decision if this change is desired labels Dec 6, 2024
@kt3k
Copy link
Member

kt3k commented Dec 20, 2024

The stack trace should point to the user's code causing the error.

This doesn't seem possible because the error is caused by badly behaving client. The user's code is not involved in the thrown error.

The server should handle TCP connection closures consistently, irrespective of the request size.

const connection = await Deno.connect({ hostname: "127.0.0.1", port: 8000 });
await connection.write(new TextEncoder().encode(request));
connection.close();

This doesn't follow HTTP protocol (as this doesn't wait for the response), and Deno.serve is http server, not tcp server. So the thrown error should be just fine.

Inconsistency of the result happens because the above badly implemented HTTP client accidentally follows HTTP protocol when the data is too small. I don't think that is an issue of Deno.serve. So I don't think there's anything we can fix about this.

The error message should be more meaningful.

I agree with this. The error message should say something about http request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ext/net related to ext/net needs investigation requires further investigation before determining if it is an issue or not triage required 👀 Deno team needs to make a decision if this change is desired
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants