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

fix(http2): Fixed HTTP2 stream closure handling #2710

Closed
wants to merge 0 commits into from

Conversation

mertcanaltin
Copy link
Member

This PR addresses an issue related to handling the closure of HTTP2 streams with the NGHTTP2_INTERNAL_ERROR code. Now, when HTTP2 streams are closed with the NGHTTP2_INTERNAL_ERROR code, the promise is rejected instead of being left unresolved.

#2675

@codecov-commenter
Copy link

codecov-commenter commented Feb 7, 2024

Codecov Report

Attention: 224 lines in your changes are missing coverage. Please review.

Comparison is base (e39a632) 85.54% compared to head (04ebf76) 85.27%.
Report is 277 commits behind head on main.

Files Patch % Lines
lib/fetch/util.js 60.41% 57 Missing ⚠️
lib/fetch/index.js 69.14% 54 Missing ⚠️
lib/handler/RetryHandler.js 74.35% 30 Missing ⚠️
lib/cache/cache.js 12.12% 29 Missing ⚠️
lib/fetch/dataURL.js 79.31% 12 Missing ⚠️
lib/api/readable.js 83.92% 9 Missing ⚠️
lib/core/diagnostics.js 84.74% 9 Missing ⚠️
lib/eventsource/eventsource.js 96.09% 5 Missing ⚠️
lib/core/request.js 94.36% 4 Missing ⚠️
lib/client.js 95.83% 3 Missing ⚠️
... and 6 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2710      +/-   ##
==========================================
- Coverage   85.54%   85.27%   -0.27%     
==========================================
  Files          76       84       +8     
  Lines        6858     7595     +737     
==========================================
+ Hits         5867     6477     +610     
- Misses        991     1118     +127     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Comment on lines 1895 to 1898
if (err.name === 'NGHTTP2_INTERNAL_ERROR') {
// Reject pending Promise when HTTP2 stream is closed
return Promise.reject(new Error('HTTP2 stream closed with NGHTTP2_INTERNAL_ERROR'))
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not part of spec. I think this issue should be resolved in undici core. Not fetch.

Copy link
Member

@ronag ronag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this issue should be resolved in undici core. Not fetch.

@mertcanaltin
Copy link
Member Author

I think this issue should be resolved in undici core. Not fetch.

greetings I have taken some steps, how can I use this in chore, I would be very happy if you have any suggestions @ronag

@mertcanaltin mertcanaltin requested a review from ronag February 8, 2024 17:26
lib/core/errors.js Outdated Show resolved Hide resolved
test/nghttp2-internal-error.js Outdated Show resolved Hide resolved
@metcoder95 metcoder95 changed the title fix(fetch): Fixed HTTP2 stream closure handling fix(http2): Fixed HTTP2 stream closure handling Feb 9, 2024
@metcoder95
Copy link
Member

Take a look at

function writeH2 (client, session, request) {
, there we manage everything related to http2

@mertcanaltin
Copy link
Member Author

@metcoder95 tried a few steps thanks for your support

lib/client.js Outdated
@@ -1840,7 +1841,9 @@ function writeH2 (client, session, request) {
})

stream.once('frameError', (type, code) => {
const err = new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`)
const errorMessage = `HTTP/2: Frame error received - type: ${type}, code: ${code}.`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding was that the 'error' event a few lines above was unhandled, and that 'frameError' is already being handled decently albeit with a different message

As far as I can tell the current changeset doesnt alter behavior other than to change the error message.

However, when I naively attempted to add errorRequest to the 'error' event handler, it resulted in a test like this failing (@metcoder95 this was the MRE you requested),

it additionally requires adding errorRequest(client, request, err) in the above stream.once('error', function (err) { handler

test('Client closing completed request should not error', async (t) => {
  const server = createSecureServer(pem, async (req, res) => {
    let body = ''
    req.setEncoding('utf8')
    res.writeHead(200, {
      'content-type': 'text/plain; charset=utf-8',
    })
    for await (const chunk of req) {
      body += chunk
    }
    res.end('hello h2')
  })

  server.listen()
  await once(server, 'listening')

  const client = new Client(`https://localhost:${server.address().port}`, {
    connect: {
      rejectUnauthorized: false,
    },
    allowH2: true,
  })

  const resp = await client.request({
    path: '/',
    method: 'GET',
  })
    

  await client.close()
  await server.close()
})

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that would fit our purpose? @jackschu

stream.once('error', function (err) {
    if (client[kHTTP2Session] && !client[kHTTP2Session].destroyed && !this.closed && !this.destroyed) {
        errorRequest(client, request, err);
        h2State.streams -= 1;
        util.destroy(stream, err);
    }
});

stream.once('frameError', (type, code) => {
    const errorMessage = `HTTP/2: Frame error received - type: ${type}, code: ${code}.`;
    const err = new HTTP2Error(errorMessage);
    errorRequest(client, request, err);
    
    if (client[kHTTP2Session] && !client[kHTTP2Session].destroyed && !this.closed && !this.destroyed) {
        h2State.streams -= 1;
        util.destroy(stream, err);
    }
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the right patch is to be honest, when I saw this in prod this.closed was true and the promise was left unresolved

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wow, sorry. Too late to the party

Let's give it a try, the frameError should also error the request that is tight to the given stream that received that error; can you also add some testing around it @mertcanaltin ?

test/http2.js Outdated Show resolved Hide resolved
@mertcanaltin
Copy link
Member Author

@metcoder95 @ronag @KhafraDev I would like to update this place, I would appreciate your comments 🙏

@ronag
Copy link
Member

ronag commented Mar 16, 2024

This is outside of my expertise. I trust @metcoder95 here.

test/http2.js Outdated
@@ -1265,6 +1265,53 @@ test('The h2 pseudo-headers is not included in the headers', async t => {
t.strictEqual(response.headers[':status'], undefined)
})

test('HTTP/2 stream closed with NGHTTP2_INTERNAL_ERROR', async (t) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you wrote a tap test. we switched to node native test runner

test/http2.js Outdated Show resolved Hide resolved
test/http2.js Outdated Show resolved Hide resolved
test/http2.js Outdated Show resolved Hide resolved
@mcollina
Copy link
Member

@mertcanaltin can you rebase this on top of main? I think there are quite a few spurious changes in there that make reviewing this quite hard via gh.

@mertcanaltin
Copy link
Member Author

I'll tidy up here, I blew my codes while synchronizing 😢

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants