| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent c956d37 commit 1377173
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1068,6 +1068,7 @@ function setupHandle(socket, type, options) { | |||
| 1068 | 1068 | if (typeof options.selectPadding === 'function') | |
| 1069 | 1069 | this[kSelectPadding] = options.selectPadding; | |
| 1070 | 1070 | handle.consume(socket._handle); | |
| 1071 | + handle.ongracefulclosecomplete = this[kMaybeDestroy].bind(this, null); | ||
| 1071 | 1072 | ||
| 1072 | 1073 | this[kHandle] = handle; | |
| 1073 | 1074 | if (this[kNativeFields]) { | |
@@ -1589,6 +1590,10 @@ class Http2Session extends EventEmitter { | |||
| 1589 | 1590 | if (typeof callback === 'function') | |
| 1590 | 1591 | this.once('close', callback); | |
| 1591 | 1592 | this.goaway(); | |
| 1593 | + const handle = this[kHandle]; | ||
| 1594 | + if (handle) { | ||
| 1595 | + handle.setGracefulClose(); | ||
| 1596 | + } | ||
| 1592 | 1597 | this[kMaybeDestroy](); | |
| 1593 | 1598 | } | |
| 1594 | 1599 | ||
@@ -1609,11 +1614,13 @@ class Http2Session extends EventEmitter { | |||
| 1609 | 1614 | // * session is closed and there are no more pending or open streams | |
| 1610 | 1615 | [kMaybeDestroy](error) { | |
| 1611 | 1616 | if (error == null) { | |
| 1617 | + const handle = this[kHandle]; | ||
| 1618 | + const hasPendingData = !!handle && handle.hasPendingData(); | ||
| 1612 | 1619 | const state = this[kState]; | |
| 1613 | 1620 | // Do not destroy if we're not closed and there are pending/open streams | |
| 1614 | 1621 | if (!this.closed || | |
| 1615 | 1622 | state.streams.size > 0 || | |
| 1616 | - state.pendingStreams.size > 0) { | ||
| 1623 | + state.pendingStreams.size > 0 || hasPendingData) { | ||
| 1617 | 1624 | return; | |
| 1618 | 1625 | } | |
| 1619 | 1626 | } | |
@@ -3300,7 +3307,7 @@ function socketOnClose() { | |||
| 3300 | 3307 | state.streams.forEach((stream) => stream.close(NGHTTP2_CANCEL)); | |
| 3301 | 3308 | state.pendingStreams.forEach((stream) => stream.close(NGHTTP2_CANCEL)); | |
| 3302 | 3309 | session.close(); | |
| 3303 | - session[kMaybeDestroy](err); | ||
| 3310 | + closeSession(session, NGHTTP2_NO_ERROR, err); | ||
| 3304 | 3311 | } | |
| 3305 | 3312 | } | |
| 3306 | 3313 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -276,6 +276,7 @@ | |||
| 276 | 276 | V(onsignal_string, "onsignal") \ | |
| 277 | 277 | V(onunpipe_string, "onunpipe") \ | |
| 278 | 278 | V(onwrite_string, "onwrite") \ | |
| 279 | + V(ongracefulclosecomplete_string, "ongracefulclosecomplete") \ | ||
| 279 | 280 | V(openssl_error_stack, "opensslErrorStack") \ | |
| 280 | 281 | V(options_string, "options") \ | |
| 281 | 282 | V(order_string, "order") \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -561,7 +561,8 @@ Http2Session::Http2Session(Http2State* http2_state, | |||
| 561 | 561 | : AsyncWrap(http2_state->env(), wrap, AsyncWrap::PROVIDER_HTTP2SESSION), | |
| 562 | 562 | js_fields_(http2_state->env()->isolate()), | |
| 563 | 563 | session_type_(type), | |
| 564 | - http2_state_(http2_state) { | ||
| 564 | + http2_state_(http2_state), | ||
| 565 | + graceful_close_initiated_(false) { | ||
| 565 | 566 | MakeWeak(); | |
| 566 | 567 | statistics_.session_type = type; | |
| 567 | 568 | statistics_.start_time = uv_hrtime(); | |
@@ -767,6 +768,24 @@ void Http2Stream::EmitStatistics() { | |||
| 767 | 768 | }); | |
| 768 | 769 | } | |
| 769 | 770 | ||
| 771 | + void Http2Session::HasPendingData(const FunctionCallbackInfo<Value>& args) { | ||
| 772 | + Http2Session* session; | ||
| 773 | + ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); | ||
| 774 | + args.GetReturnValue().Set(session->HasPendingData()); | ||
| 775 | + } | ||
| 776 | + | ||
| 777 | + bool Http2Session::HasPendingData() const { | ||
| 778 | + nghttp2_session* session = session_.get(); | ||
| 779 | + int want_write = nghttp2_session_want_write(session); | ||
| 780 | + // It is expected that want_read will alway be 0 if graceful | ||
| 781 | + // session close is initiated and goaway frame is sent. | ||
| 782 | + int want_read = nghttp2_session_want_read(session); | ||
| 783 | + if (want_write == 0 && want_read == 0) { | ||
| 784 | + return false; | ||
| 785 | + } | ||
| 786 | + return true; | ||
| 787 | + } | ||
| 788 | + | ||
| 770 | 789 | void Http2Session::EmitStatistics() { | |
| 771 | 790 | if (!HasHttp2Observer(env())) [[likely]] { | |
| 772 | 791 | return; | |
@@ -1745,6 +1764,7 @@ void Http2Session::HandleSettingsFrame(const nghttp2_frame* frame) { | |||
| 1745 | 1764 | void Http2Session::OnStreamAfterWrite(WriteWrap* w, int status) { | |
| 1746 | 1765 | Debug(this, "write finished with status %d", status); | |
| 1747 | 1766 | ||
| 1767 | + MaybeNotifyGracefulCloseComplete(); | ||
| 1748 | 1768 | CHECK(is_write_in_progress()); | |
| 1749 | 1769 | set_write_in_progress(false); | |
| 1750 | 1770 | ||
@@ -1967,6 +1987,7 @@ uint8_t Http2Session::SendPendingData() { | |||
| 1967 | 1987 | if (!res.async) { | |
| 1968 | 1988 | set_write_in_progress(false); | |
| 1969 | 1989 | ClearOutgoing(res.err); | |
| 1990 | + MaybeNotifyGracefulCloseComplete(); | ||
| 1970 | 1991 | } | |
| 1971 | 1992 | ||
| 1972 | 1993 | MaybeStopReading(); | |
@@ -3478,6 +3499,8 @@ void Initialize(Local<Object> target, | |||
| 3478 | 3499 | SetProtoMethod(isolate, session, "receive", Http2Session::Receive); | |
| 3479 | 3500 | SetProtoMethod(isolate, session, "destroy", Http2Session::Destroy); | |
| 3480 | 3501 | SetProtoMethod(isolate, session, "goaway", Http2Session::Goaway); | |
| 3502 | + SetProtoMethod( | ||
| 3503 | + isolate, session, "hasPendingData", Http2Session::HasPendingData); | ||
| 3481 | 3504 | SetProtoMethod(isolate, session, "settings", Http2Session::Settings); | |
| 3482 | 3505 | SetProtoMethod(isolate, session, "request", Http2Session::Request); | |
| 3483 | 3506 | SetProtoMethod( | |
@@ -3498,6 +3521,8 @@ void Initialize(Local<Object> target, | |||
| 3498 | 3521 | "remoteSettings", | |
| 3499 | 3522 | Http2Session::RefreshSettings<nghttp2_session_get_remote_settings, | |
| 3500 | 3523 | false>); | |
| 3524 | + SetProtoMethod( | ||
| 3525 | + isolate, session, "setGracefulClose", Http2Session::SetGracefulClose); | ||
| 3501 | 3526 | SetConstructorFunction(context, target, "Http2Session", session); | |
| 3502 | 3527 | ||
| 3503 | 3528 | Local<Object> constants = Object::New(isolate); | |
@@ -3552,6 +3577,38 @@ void Initialize(Local<Object> target, | |||
| 3552 | 3577 | nghttp2_set_debug_vprintf_callback(NgHttp2Debug); | |
| 3553 | 3578 | #endif | |
| 3554 | 3579 | } | |
| 3580 | + | ||
| 3581 | + void Http2Session::SetGracefulClose(const FunctionCallbackInfo<Value>& args) { | ||
| 3582 | + Http2Session* session; | ||
| 3583 | + ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); | ||
| 3584 | + CHECK_NOT_NULL(session); | ||
| 3585 | + // Set the graceful close flag | ||
| 3586 | + session->SetGracefulCloseInitiated(true); | ||
| 3587 | + | ||
| 3588 | + Debug(session, "Setting graceful close initiated flag"); | ||
| 3589 | + } | ||
| 3590 | + | ||
| 3591 | + void Http2Session::MaybeNotifyGracefulCloseComplete() { | ||
| 3592 | + nghttp2_session* session = session_.get(); | ||
| 3593 | + | ||
| 3594 | + if (!IsGracefulCloseInitiated()) { | ||
| 3595 | + return; | ||
| 3596 | + } | ||
| 3597 | + | ||
| 3598 | + int want_write = nghttp2_session_want_write(session); | ||
| 3599 | + int want_read = nghttp2_session_want_read(session); | ||
| 3600 | + bool should_notify = (want_write == 0 && want_read == 0); | ||
| 3601 | + | ||
| 3602 | + if (should_notify) { | ||
| 3603 | + Debug(this, "Notifying JS after write in graceful close mode"); | ||
| 3604 | + | ||
| 3605 | + // Make the callback to JavaScript | ||
| 3606 | + HandleScope scope(env()->isolate()); | ||
| 3607 | + MakeCallback(env()->ongracefulclosecomplete_string(), 0, nullptr); | ||
| 3608 | + } | ||
| 3609 | + | ||
| 3610 | + return; | ||
| 3611 | + } | ||
| 3555 | 3612 | } // namespace http2 | |
| 3556 | 3613 | } // namespace node | |
| 3557 | 3614 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -712,6 +712,7 @@ class Http2Session : public AsyncWrap, | |||
| 712 | 712 | static void Consume(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 713 | 713 | static void Receive(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 714 | 714 | static void Destroy(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 715 | + static void HasPendingData(const v8::FunctionCallbackInfo<v8::Value>& args); | ||
| 715 | 716 | static void Settings(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 716 | 717 | static void Request(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 717 | 718 | static void SetNextStreamID(const v8::FunctionCallbackInfo<v8::Value>& args); | |
@@ -723,6 +724,7 @@ class Http2Session : public AsyncWrap, | |||
| 723 | 724 | static void Ping(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 724 | 725 | static void AltSvc(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 725 | 726 | static void Origin(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 727 | + static void SetGracefulClose(const v8::FunctionCallbackInfo<v8::Value>& args); | ||
| 726 | 728 | ||
| 727 | 729 | template <get_setting fn, bool local> | |
| 728 | 730 | static void RefreshSettings(const v8::FunctionCallbackInfo<v8::Value>& args); | |
@@ -735,6 +737,7 @@ class Http2Session : public AsyncWrap, | |||
| 735 | 737 | ||
| 736 | 738 | BaseObjectPtr<Http2Ping> PopPing(); | |
| 737 | 739 | bool AddPing(const uint8_t* data, v8::Local<v8::Function> callback); | |
| 740 | + bool HasPendingData() const; | ||
| 738 | 741 | ||
| 739 | 742 | BaseObjectPtr<Http2Settings> PopSettings(); | |
| 740 | 743 | bool AddSettings(v8::Local<v8::Function> callback); | |
@@ -785,6 +788,13 @@ class Http2Session : public AsyncWrap, | |||
| 785 | 788 | ||
| 786 | 789 | Statistics statistics_ = {}; | |
| 787 | 790 | ||
| 791 | + bool IsGracefulCloseInitiated() const { | ||
| 792 | + return graceful_close_initiated_; | ||
| 793 | + } | ||
| 794 | + void SetGracefulCloseInitiated(bool value) { | ||
| 795 | + graceful_close_initiated_ = value; | ||
| 796 | + } | ||
| 797 | + | ||
| 788 | 798 | private: | |
| 789 | 799 | void EmitStatistics(); | |
| 790 | 800 | ||
@@ -951,8 +961,13 @@ class Http2Session : public AsyncWrap, | |||
| 951 | 961 | void CopyDataIntoOutgoing(const uint8_t* src, size_t src_length); | |
| 952 | 962 | void ClearOutgoing(int status); | |
| 953 | 963 | ||
| 964 | + void MaybeNotifyGracefulCloseComplete(); | ||
| 965 | + | ||
| 954 | 966 | friend class Http2Scope; | |
| 955 | 967 | friend class Http2StreamListener; | |
| 968 | + | ||
| 969 | + // Flag to indicate that JavaScript has initiated a graceful closure | ||
| 970 | + bool graceful_close_initiated_ = false; | ||
| 956 | 971 | }; | |
| 957 | 972 | ||
| 958 | 973 | struct Http2SessionPerformanceEntryTraits { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -5,16 +5,23 @@ if (!common.hasCrypto) | |||
| 5 | 5 | common.skip('missing crypto'); | |
| 6 | 6 | const assert = require('assert'); | |
| 7 | 7 | const h2 = require('http2'); | |
| 8 | + let client; | ||
| 8 | 9 | ||
| 9 | 10 | const server = h2.createServer(); | |
| 10 | 11 | server.on('stream', (stream) => { | |
| 11 | - stream.on('close', common.mustCall()); | ||
| 12 | - stream.respond(); | ||
| 13 | - stream.end('ok'); | ||
| 12 | + stream.on('close', common.mustCall(() => { | ||
| 13 | + client.close(); | ||
| 14 | + server.close(); | ||
| 15 | + })); | ||
| 16 | + stream.on('error', common.expectsError({ | ||
| 17 | + code: 'ERR_HTTP2_STREAM_ERROR', | ||
| 18 | + name: 'Error', | ||
| 19 | + message: 'Stream closed with error code NGHTTP2_PROTOCOL_ERROR' | ||
| 20 | + })); | ||
| 14 | 21 | }); | |
| 15 | 22 | ||
| 16 | 23 | server.listen(0, common.mustCall(() => { | |
| 17 | - const client = h2.connect(`http://localhost:${server.address().port}`); | ||
| 24 | + client = h2.connect(`http://localhost:${server.address().port}`); | ||
| 18 | 25 | const req = client.request(); | |
| 19 | 26 | const closeCode = 1; | |
| 20 | 27 | ||
@@ -52,8 +59,6 @@ server.listen(0, common.mustCall(() => { | |||
| 52 | 59 | req.on('close', common.mustCall(() => { | |
| 53 | 60 | assert.strictEqual(req.destroyed, true); | |
| 54 | 61 | assert.strictEqual(req.rstCode, closeCode); | |
| 55 | - server.close(); | ||
| 56 | - client.close(); | ||
| 57 | 62 | })); | |
| 58 | 63 | ||
| 59 | 64 | req.on('error', common.expectsError({ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,48 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common'); | ||
| 4 | + if (!common.hasCrypto) | ||
| 5 | + common.skip('missing crypto'); | ||
| 6 | + const assert = require('assert'); | ||
| 7 | + const h2 = require('http2'); | ||
| 8 | + | ||
| 9 | + const server = h2.createServer(); | ||
| 10 | + let session; | ||
| 11 | + | ||
| 12 | + server.on('session', common.mustCall(function(s) { | ||
| 13 | + session = s; | ||
| 14 | + session.on('close', common.mustCall(function() { | ||
| 15 | + server.close(); | ||
| 16 | + })); | ||
| 17 | + })); | ||
| 18 | + | ||
| 19 | + server.listen(0, common.mustCall(function() { | ||
| 20 | + const port = server.address().port; | ||
| 21 | + | ||
| 22 | + const url = `http://localhost:${port}`; | ||
| 23 | + const client = h2.connect(url, common.mustCall(function() { | ||
| 24 | + const headers = { | ||
| 25 | + ':path': '/', | ||
| 26 | + ':method': 'GET', | ||
| 27 | + ':scheme': 'http', | ||
| 28 | + ':authority': `localhost:${port}` | ||
| 29 | + }; | ||
| 30 | + const request = client.request(headers); | ||
| 31 | + request.on('response', common.mustCall(function(headers) { | ||
| 32 | + assert.strictEqual(headers[':status'], 200); | ||
| 33 | + }, 1)); | ||
| 34 | + request.on('end', common.mustCall(function() { | ||
| 35 | + client.close(); | ||
| 36 | + })); | ||
| 37 | + request.end(); | ||
| 38 | + request.resume(); | ||
| 39 | + })); | ||
| 40 | + client.on('goaway', common.mustCallAtLeast(1)); | ||
| 41 | + })); | ||
| 42 | + | ||
| 43 | + server.once('request', common.mustCall(function(request, response) { | ||
| 44 | + response.on('finish', common.mustCall(function() { | ||
| 45 | + session.close(); | ||
| 46 | + })); | ||
| 47 | + response.end(); | ||
| 48 | + })); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments