grantila / fetch-h2

HTTP/1+2 Fetch API client for Node.js
MIT License
336 stars 16 forks source link

Error [ERR_HTTP2_INVALID_SESSION]: The session has been destroyed #88

Open asppsa opened 4 years ago

asppsa commented 4 years ago

I've got a long-running node application that occasionally makes a flurry of requests to an AWS server. If the application has been sitting dormant for a few minutes, and then makes a request, the application crashes with an error:

Error [ERR_HTTP2_INVALID_SESSION]: The session has been destroyed
    at ClientHttp2Session.request (internal/http2/core.js:1560:13)
    at doFetch (/Users/apharo/Blake/content-build-tools/node_modules/fetch-h2/dist/lib/fetch-http2.js:49:32)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (internal/process/task_queues.js:94:5)

Using fetch-h2 2.4.0 with NodeJS v12.15.0. At this stage I'm not sure if the crash is avoidable or not - I'll see if I can create a simple repro of the issue.

stefan-guggisberg commented 3 years ago

We're experiencing the same issue. We're using fetch-h2 in long-running code, sending frequent requests to some HTTP/2 backend (Azure Front Door for example).

I can reproduce the problem by sending the same request in an endless loop in certain intervals (e.g. every 5 seconds), something like

const { fetch, disconnectAll } = require('fetch-h2');

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const url = '<some http/2 server url>';

const main = async () => {
  let cnt = 1;
  let errCnt = 0;
  const MAX_ERRORS = 10;
  while (true) {
    console.log(`iteration #${cnt++}, ts: ${new Date().toISOString().slice(11, 23)} UTC`);
    try {
      const resp = await fetch(url);
      console.log(`status: ${resp.status}, httpVersion: ${resp.httpVersion}`);
      const data = await resp.text();
      console.log(`response body: ${data}\n\n`);
    } catch (err) {
      errCnt++;
      console.log(`caught error #${errCnt}: ${err}`);
      console.error(err);
      if (errCnt > MAX_ERRORS) {
        console.log('rethrowing ...');
        throw err;
      }
    }
    await sleep(5000);
  }
};

main().then(() => {
  console.log('cleaning up...');
  return disconnectAll();
}).catch((err) => {
  console.log(`caught error: ${err}`);
  console.error(err);
});

After some time (usually a couple of hours) we're seeing first an ECONNRESET error followed by ERR_HTTP2_INVALID_SESSION errors which it doesn't recover from:

...
iteration #12035, ts: 13:18:04.453 UTC
status: 200, httpVersion: 2
iteration #12036, ts: 13:18:10.546 UTC
caught error #1: Error: read ECONNRESET
Error: read ECONNRESET
    at TLSWrap.onStreamRead (internal/stream_base_commons.js:205:27) {
  errno: 'ECONNRESET',
  code: 'ECONNRESET',
  syscall: 'read'
}
iteration #12037, ts: 13:18:15.574 UTC
caught error #2: Error [ERR_HTTP2_INVALID_SESSION]: The session has been destroyed
Error [ERR_HTTP2_INVALID_SESSION]: The session has been destroyed
    at ClientHttp2Session.request (internal/http2/core.js:1587:13)
    at doFetch (/projects/fetch-test/node_modules/fetch-h2/dist/lib/fetch-http2.js:51:32) {
  code: 'ERR_HTTP2_INVALID_SESSION'
}

The problem seems to be that the cached http2 session in fetch-h2 is not discarded once it errors and subsequent requests to the same origin will result in ERR_HTTP2_INVALID_SESSION errors.

cool-firer commented 5 months ago

is this problem solved? I met the same error.