From 0b74980e235e126f4ce2262a15c856cd40a958ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 27 Sep 2023 14:52:34 +0200 Subject: [PATCH 01/15] fix(node/http2): use options.createConnection --- ext/node/polyfills/http2.ts | 65 +++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts index 62dd1a501b50a3..1f83ceddabcd7c 100644 --- a/ext/node/polyfills/http2.ts +++ b/ext/node/polyfills/http2.ts @@ -11,6 +11,7 @@ import { Buffer } from "node:buffer"; import { Server, Socket, TCP } from "node:net"; import { TypedArray } from "ext:deno_node/internal/util/types.ts"; import { + kHandle, kMaybeDestroy, kUpdateTimer, setStreamTimeout, @@ -83,7 +84,7 @@ const SESSION_FLAGS_DESTROYED = 0x4; const ENCODER = new TextEncoder(); type Http2Headers = Record; -const debugHttp2Enabled = false; +const debugHttp2Enabled = true; function debugHttp2(...args) { if (debugHttp2Enabled) { console.log(...args); @@ -264,7 +265,7 @@ export class Http2Session extends EventEmitter { } setTimeout(msecs: number, callback?: () => void) { - setStreamTimeout(this, msecs, callback); + setStreamTimeout.call(this, msecs, callback); } } @@ -302,6 +303,7 @@ function closeSession(session: Http2Session, code?: number, error?: Error) { session[kDenoConnRid], session[kDenoClientRid], ); + console.table(Deno.resources()); core.tryClose(session[kDenoConnRid]); core.tryClose(session[kDenoClientRid]); @@ -354,9 +356,10 @@ export class ClientHttp2Session extends Http2Session { // TODO(bartlomieju): cleanup this.#connectPromise = (async () => { debugHttp2(">>> before connect"); - const conn = await connPromise; - const [clientRid, connRid] = await op_http2_connect(conn.rid, url); - debugHttp2(">>> after connect"); + const connRid_ = await connPromise; + console.log(">>>> awaited connRid", connRid_); + const [clientRid, connRid] = await op_http2_connect(connRid_, url); + debugHttp2(">>> after connect", clientRid, connRid); this[kDenoClientRid] = clientRid; this[kDenoConnRid] = connRid; // TODO(bartlomieju): save this promise, so the session can be unrefed @@ -1190,7 +1193,9 @@ function finishCloseStream(stream, code) { ); core.tryClose(stream[kDenoRid]); core.tryClose(stream[kDenoResponse].bodyRid); - stream.emit("close"); + nextTick(() => { + stream.emit("close"); + }); }).catch(() => { debugHttp2( ">>> finishCloseStream close2 catch", @@ -1199,7 +1204,11 @@ function finishCloseStream(stream, code) { ); core.tryClose(stream[kDenoRid]); core.tryClose(stream[kDenoResponse].bodyRid); - stream.emit("close"); + nextTick(() => { + nextTick(() => { + stream.emit("close"); + }); + }); }); } } @@ -1488,24 +1497,46 @@ export function connect( host = authority.host; } + let conn, nodeConn, url; + // TODO(bartlomieju): handle defaults if (typeof options.createConnection === "function") { - console.error("Not implemented: http2.connect.options.createConnection"); + // console.error("Not implemented: http2.connect.options.createConnection"); // notImplemented("http2.connect.options.createConnection"); + nodeConn = options.createConnection(authority, options); + } else { + // TODO(bartlomieju): using `localhost` as a `host` refuses to connect + // when server is listening on `0.0.0.0` + console.log(protocol, host, port); + + if (protocol == "http:") { + conn = Deno.connect({ port, hostname: "127.0.0.1" }); + url = `http://${host}${port == 80 ? "" : (":" + port)}`; + } else if (protocol == "https:") { + conn = Deno.connectTls({ port, hostname: host, alpnProtocols: ["h2"] }); + url = `http://${host}${port == 443 ? "" : (":" + port)}`; + } else { + throw new TypeError("Unexpected URL protocol"); + } } - let conn, url; - if (protocol == "http:") { - conn = Deno.connect({ port, hostname: host }); - url = `http://${host}${port == 80 ? "" : (":" + port)}`; - } else if (protocol == "https:") { - conn = Deno.connectTls({ port, hostname: host, alpnProtocols: ["h2"] }); - url = `http://${host}${port == 443 ? "" : (":" + port)}`; + let connPromise; + + if (nodeConn) { + connPromise = new Promise((resolve) => { + nodeConn.once("connect", () => { + const rid = nodeConn[kHandle][kStreamBaseField].rid; + resolve(rid); + }); + }); } else { - throw new TypeError("Unexpected URL protocol"); + connPromise = (async () => { + const c = await conn; + return c.rid; + })(); } - const session = new ClientHttp2Session(conn, url, options); + const session = new ClientHttp2Session(connPromise, url, options); session[kAuthority] = `${options.servername || host}:${port}`; session[kProtocol] = protocol; From 585f73f272e6dab34e4df2814e125bb5407dffb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 27 Sep 2023 16:13:31 +0200 Subject: [PATCH 02/15] progress, but startConnecting is called too few times --- ext/node/polyfills/http2.ts | 31 ++++++++++++++++--- .../polyfills/internal_binding/tcp_wrap.ts | 8 +++-- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts index 1f83ceddabcd7c..e9c4a8f2c16e7c 100644 --- a/ext/node/polyfills/http2.ts +++ b/ext/node/polyfills/http2.ts @@ -304,8 +304,12 @@ function closeSession(session: Http2Session, code?: number, error?: Error) { session[kDenoClientRid], ); console.table(Deno.resources()); - core.tryClose(session[kDenoConnRid]); - core.tryClose(session[kDenoClientRid]); + if (session[kDenoConnRid]) { + core.tryClose(session[kDenoConnRid]); + } + if (session[kDenoClientRid]) { + core.tryClose(session[kDenoClientRid]); + } finishSessionClose(session, error); } @@ -357,7 +361,7 @@ export class ClientHttp2Session extends Http2Session { this.#connectPromise = (async () => { debugHttp2(">>> before connect"); const connRid_ = await connPromise; - console.log(">>>> awaited connRid", connRid_); + console.log(">>>> awaited connRid", connRid_, url); const [clientRid, connRid] = await op_http2_connect(connRid_, url); debugHttp2(">>> after connect", clientRid, connRid); this[kDenoClientRid] = clientRid; @@ -1499,11 +1503,13 @@ export function connect( let conn, nodeConn, url; + console.log("authority", authority); // TODO(bartlomieju): handle defaults if (typeof options.createConnection === "function") { // console.error("Not implemented: http2.connect.options.createConnection"); // notImplemented("http2.connect.options.createConnection"); - nodeConn = options.createConnection(authority, options); + nodeConn = options.createConnection(host, options); + url = `http://${host}${port == 80 ? "" : (":" + port)}`; } else { // TODO(bartlomieju): using `localhost` as a `host` refuses to connect // when server is listening on `0.0.0.0` @@ -1514,7 +1520,7 @@ export function connect( url = `http://${host}${port == 80 ? "" : (":" + port)}`; } else if (protocol == "https:") { conn = Deno.connectTls({ port, hostname: host, alpnProtocols: ["h2"] }); - url = `http://${host}${port == 443 ? "" : (":" + port)}`; + url = `https://${host}${port == 443 ? "" : (":" + port)}`; } else { throw new TypeError("Unexpected URL protocol"); } @@ -1523,6 +1529,7 @@ export function connect( let connPromise; if (nodeConn) { + nodeConn.on("error", socketOnError); connPromise = new Promise((resolve) => { nodeConn.once("connect", () => { const rid = nodeConn[kHandle][kStreamBaseField].rid; @@ -1537,6 +1544,9 @@ export function connect( } const session = new ClientHttp2Session(connPromise, url, options); + if (nodeConn) { + nodeConn[kSession] = session; + } session[kAuthority] = `${options.servername || host}:${port}`; session[kProtocol] = protocol; @@ -1546,6 +1556,17 @@ export function connect( return session; } +function socketOnError(error) { + const session = this[kSession]; + if (session !== undefined) { + if (error.code === "ECONNRESET" && session[kState].goawayCode !== null) { + return session.destroy(); + } + debugHttp2(">>>> socket error", error); + session.destroy(error); + } +} + export const constants = { NGHTTP2_ERR_FRAME_SIZE_ERROR: -522, NGHTTP2_NV_FLAG_NONE: 0, diff --git a/ext/node/polyfills/internal_binding/tcp_wrap.ts b/ext/node/polyfills/internal_binding/tcp_wrap.ts index 3725b632507b2c..da3e41d4ac5dec 100644 --- a/ext/node/polyfills/internal_binding/tcp_wrap.ts +++ b/ext/node/polyfills/internal_binding/tcp_wrap.ts @@ -380,16 +380,20 @@ export class TCP extends ConnectionWrap { this[kStreamBaseField] = conn; try { + console.log("here"); this.afterConnect(req, 0); - } catch { + } catch (e) { + console.log("here2", e); // swallow callback errors. } }, () => { try { + console.log("here3"); // TODO(cmorten): correct mapping of connection error to status code. this.afterConnect(req, codeMap.get("ECONNREFUSED")!); - } catch { + } catch (e) { + console.log("here4", e); // swallow callback errors. } }, From 5df3c700f262ba0c63eec764c5a05e31d6ed7226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 27 Sep 2023 17:02:42 +0200 Subject: [PATCH 03/15] socketOnClose --- ext/node/polyfills/http2.ts | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts index e9c4a8f2c16e7c..26fe2c1ca01420 100644 --- a/ext/node/polyfills/http2.ts +++ b/ext/node/polyfills/http2.ts @@ -38,6 +38,7 @@ import { ERR_HTTP2_TRAILERS_ALREADY_SENT, ERR_HTTP2_TRAILERS_NOT_READY, ERR_INVALID_HTTP_TOKEN, + ERR_SOCKET_CLOSED, } from "ext:deno_node/internal/errors.ts"; import { _checkIsHttpToken } from "ext:deno_node/_http_common.ts"; import { TcpConn } from "ext:deno_net/01_net.js"; @@ -206,8 +207,12 @@ export class Http2Session extends EventEmitter { _opaqueData: Buffer | TypedArray | DataView, ) { warnNotImplemented("Http2Session.goaway"); - core.tryClose(this[kDenoConnRid]); - core.tryClose(this[kDenoClientRid]); + if (this[kDenoConnRid]) { + core.tryClose(this[kDenoConnRid]); + } + if (this[kDenoClientRid]) { + core.tryClose(this[kDenoClientRid]); + } } destroy(error = constants.NGHTTP2_NO_ERROR, code?: number) { @@ -1530,6 +1535,7 @@ export function connect( if (nodeConn) { nodeConn.on("error", socketOnError); + nodeConn.on("close", socketOnClose); connPromise = new Promise((resolve) => { nodeConn.once("connect", () => { const rid = nodeConn[kHandle][kStreamBaseField].rid; @@ -1567,6 +1573,21 @@ function socketOnError(error) { } } +function socketOnClose() { + const session = this[kSession]; + if (session !== undefined) { + debugHttp2(">>>> socket closed"); + const err = session.connecting ? new ERR_SOCKET_CLOSED() : null; + const state = session[kState]; + state.streams.forEach((stream) => stream.close(constants.NGHTTP2_CANCEL)); + state.pendingStreams.forEach((stream) => + stream.close(constants.NGHTTP2_CANCEL) + ); + session.close(); + session[kMaybeDestroy](err); + } +} + export const constants = { NGHTTP2_ERR_FRAME_SIZE_ERROR: -522, NGHTTP2_NV_FLAG_NONE: 0, From 05ff68e88047230dc0915af576a8f7c028940e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 27 Sep 2023 23:30:41 +0200 Subject: [PATCH 04/15] basic unrefing --- ext/node/polyfills/http2.ts | 25 ++++++++++++++++++++++++- ext/web/02_timers.js | 4 +++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts index 26fe2c1ca01420..37596629b49a11 100644 --- a/ext/node/polyfills/http2.ts +++ b/ext/node/polyfills/http2.ts @@ -68,6 +68,7 @@ const kDenoResponse = Symbol("kDenoResponse"); const kDenoRid = Symbol("kDenoRid"); const kDenoClientRid = Symbol("kDenoClientRid"); const kDenoConnRid = Symbol("kDenoConnRid"); +const kPollConnPromiseId = Symbol("kPollConnPromiseId"); const STREAM_FLAGS_PENDING = 0x0; const STREAM_FLAGS_READY = 0x1; @@ -351,6 +352,7 @@ function assertValidPseudoHeader(header: string) { export class ClientHttp2Session extends Http2Session { #connectPromise: Promise; + #refed = true; constructor( connPromise: Promise | Promise, @@ -361,6 +363,7 @@ export class ClientHttp2Session extends Http2Session { this[kPendingRequestCalls] = null; this[kDenoClientRid] = undefined; this[kDenoConnRid] = undefined; + this[kPollConnPromiseId] = undefined; // TODO(bartlomieju): cleanup this.#connectPromise = (async () => { @@ -374,10 +377,16 @@ export class ClientHttp2Session extends Http2Session { // TODO(bartlomieju): save this promise, so the session can be unrefed (async () => { try { - await core.opAsync( + const promise = core.opAsync( "op_http2_poll_client_connection", this[kDenoConnRid], ); + this[kPollConnPromiseId] = + promise[Symbol.for("Deno.core.internalPromiseId")]; + if (!this.#refed) { + this.unref(); + } + await promise; } catch (e) { this.emit("error", e); } @@ -386,6 +395,20 @@ export class ClientHttp2Session extends Http2Session { })(); } + ref() { + this.#refed = true; + if (this[kPollConnPromiseId]) { + core.refOp(this[kPollConnPromiseId]); + } + } + + unref() { + this.#refed = false; + if (this[kPollConnPromiseId]) { + core.unrefOp(this[kPollConnPromiseId]); + } + } + request( headers: Http2Headers, options?: Record, diff --git a/ext/web/02_timers.js b/ext/web/02_timers.js index aa7e74673be495..76b0a740530bf1 100644 --- a/ext/web/02_timers.js +++ b/ext/web/02_timers.js @@ -329,7 +329,7 @@ function setTimeout(callback, timeout = 0, ...args) { callback = webidl.converters.DOMString(callback); } timeout = webidl.converters.long(timeout); - + console.log(">>>> setTimeout"); return initializeTimer(callback, timeout, args, false); } @@ -371,6 +371,7 @@ function clearInterval(id = 0) { } function refTimer(id) { + console.log(">>>> refTimer"); const timerInfo = MapPrototypeGet(activeTimers, id); if (timerInfo === undefined || timerInfo.isRef) { return; @@ -380,6 +381,7 @@ function refTimer(id) { } function unrefTimer(id) { + console.log(">>>> unrefTimer"); const timerInfo = MapPrototypeGet(activeTimers, id); if (timerInfo === undefined || !timerInfo.isRef) { return; From f3af6430ac07d3d9d7230ac74db04bf57b95bfe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 7 Oct 2023 00:13:08 +0200 Subject: [PATCH 05/15] revert unrelated change --- ext/node/polyfills/internal_binding/tcp_wrap.ts | 8 ++------ ext/web/02_timers.js | 4 +--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/ext/node/polyfills/internal_binding/tcp_wrap.ts b/ext/node/polyfills/internal_binding/tcp_wrap.ts index da3e41d4ac5dec..3725b632507b2c 100644 --- a/ext/node/polyfills/internal_binding/tcp_wrap.ts +++ b/ext/node/polyfills/internal_binding/tcp_wrap.ts @@ -380,20 +380,16 @@ export class TCP extends ConnectionWrap { this[kStreamBaseField] = conn; try { - console.log("here"); this.afterConnect(req, 0); - } catch (e) { - console.log("here2", e); + } catch { // swallow callback errors. } }, () => { try { - console.log("here3"); // TODO(cmorten): correct mapping of connection error to status code. this.afterConnect(req, codeMap.get("ECONNREFUSED")!); - } catch (e) { - console.log("here4", e); + } catch { // swallow callback errors. } }, diff --git a/ext/web/02_timers.js b/ext/web/02_timers.js index 76b0a740530bf1..aa7e74673be495 100644 --- a/ext/web/02_timers.js +++ b/ext/web/02_timers.js @@ -329,7 +329,7 @@ function setTimeout(callback, timeout = 0, ...args) { callback = webidl.converters.DOMString(callback); } timeout = webidl.converters.long(timeout); - console.log(">>>> setTimeout"); + return initializeTimer(callback, timeout, args, false); } @@ -371,7 +371,6 @@ function clearInterval(id = 0) { } function refTimer(id) { - console.log(">>>> refTimer"); const timerInfo = MapPrototypeGet(activeTimers, id); if (timerInfo === undefined || timerInfo.isRef) { return; @@ -381,7 +380,6 @@ function refTimer(id) { } function unrefTimer(id) { - console.log(">>>> unrefTimer"); const timerInfo = MapPrototypeGet(activeTimers, id); if (timerInfo === undefined || !timerInfo.isRef) { return; From 401a437fbb222f0db94287c9561807a343357f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 7 Oct 2023 00:13:45 +0200 Subject: [PATCH 06/15] disable log, use only one tick --- ext/node/polyfills/http2.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts index 37596629b49a11..065ac4574f2d42 100644 --- a/ext/node/polyfills/http2.ts +++ b/ext/node/polyfills/http2.ts @@ -86,7 +86,7 @@ const SESSION_FLAGS_DESTROYED = 0x4; const ENCODER = new TextEncoder(); type Http2Headers = Record; -const debugHttp2Enabled = true; +const debugHttp2Enabled = false; function debugHttp2(...args) { if (debugHttp2Enabled) { console.log(...args); @@ -1237,9 +1237,7 @@ function finishCloseStream(stream, code) { core.tryClose(stream[kDenoRid]); core.tryClose(stream[kDenoResponse].bodyRid); nextTick(() => { - nextTick(() => { - stream.emit("close"); - }); + stream.emit("close"); }); }); } From 1dafa5b9ca7bf0e36298080da1f1fcb6659a4221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 7 Oct 2023 00:49:38 +0200 Subject: [PATCH 07/15] unify to use Node connect APIs --- ext/node/polyfills/http2.ts | 76 ++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 43 deletions(-) diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts index 065ac4574f2d42..1e53a079504b6b 100644 --- a/ext/node/polyfills/http2.ts +++ b/ext/node/polyfills/http2.ts @@ -8,7 +8,8 @@ const core = globalThis.Deno.core; import { notImplemented, warnNotImplemented } from "ext:deno_node/_utils.ts"; import { EventEmitter } from "node:events"; import { Buffer } from "node:buffer"; -import { Server, Socket, TCP } from "node:net"; +import { connect as netConnect, Server, Socket, TCP } from "node:net"; +import { connect as tlsConnect } from "node:tls"; import { TypedArray } from "ext:deno_node/internal/util/types.ts"; import { kHandle, @@ -37,6 +38,7 @@ import { ERR_HTTP2_STREAM_ERROR, ERR_HTTP2_TRAILERS_ALREADY_SENT, ERR_HTTP2_TRAILERS_NOT_READY, + ERR_HTTP2_UNSUPPORTED_PROTOCOL, ERR_INVALID_HTTP_TOKEN, ERR_SOCKET_CLOSED, } from "ext:deno_node/internal/errors.ts"; @@ -355,7 +357,8 @@ export class ClientHttp2Session extends Http2Session { #refed = true; constructor( - connPromise: Promise | Promise, + // deno-lint-ignore no-explicit-any + socket: any, url: string, options: Record, ) { @@ -365,16 +368,27 @@ export class ClientHttp2Session extends Http2Session { this[kDenoConnRid] = undefined; this[kPollConnPromiseId] = undefined; + socket.on("error", socketOnError); + socket.on("close", socketOnClose); + const connPromise = new Promise((resolve) => { + const eventName = url.startsWith("https") ? "secureConnect" : "connect"; + console.log("eventName", eventName, url); + socket.once(eventName, () => { + const rid = socket[kHandle][kStreamBaseField].rid; + resolve(rid); + }); + }); + socket[kSession] = this; + // TODO(bartlomieju): cleanup this.#connectPromise = (async () => { debugHttp2(">>> before connect"); const connRid_ = await connPromise; - console.log(">>>> awaited connRid", connRid_, url); + // console.log(">>>> awaited connRid", connRid_, url); const [clientRid, connRid] = await op_http2_connect(connRid_, url); debugHttp2(">>> after connect", clientRid, connRid); this[kDenoClientRid] = clientRid; this[kDenoConnRid] = connRid; - // TODO(bartlomieju): save this promise, so the session can be unrefed (async () => { try { const promise = core.opAsync( @@ -1527,53 +1541,29 @@ export function connect( host = authority.host; } - let conn, nodeConn, url; + let url, socket; - console.log("authority", authority); - // TODO(bartlomieju): handle defaults if (typeof options.createConnection === "function") { - // console.error("Not implemented: http2.connect.options.createConnection"); - // notImplemented("http2.connect.options.createConnection"); - nodeConn = options.createConnection(host, options); url = `http://${host}${port == 80 ? "" : (":" + port)}`; + socket = options.createConnection(host, options); } else { - // TODO(bartlomieju): using `localhost` as a `host` refuses to connect - // when server is listening on `0.0.0.0` - console.log(protocol, host, port); - - if (protocol == "http:") { - conn = Deno.connect({ port, hostname: "127.0.0.1" }); - url = `http://${host}${port == 80 ? "" : (":" + port)}`; - } else if (protocol == "https:") { - conn = Deno.connectTls({ port, hostname: host, alpnProtocols: ["h2"] }); - url = `https://${host}${port == 443 ? "" : (":" + port)}`; - } else { - throw new TypeError("Unexpected URL protocol"); + switch (protocol) { + case "http:": + url = `http://${host}${port == 80 ? "" : (":" + port)}`; + socket = netConnect({ port, host, ...options }); + break; + case "https:": + // TODO(bartlomieju): handle `initializeTLSOptions` here + url = `https://${host}${port == 443 ? "" : (":" + port)}`; + socket = tlsConnect(port, host, {}); + break; + default: + throw new ERR_HTTP2_UNSUPPORTED_PROTOCOL(protocol); } } - let connPromise; + const session = new ClientHttp2Session(socket, url, options); - if (nodeConn) { - nodeConn.on("error", socketOnError); - nodeConn.on("close", socketOnClose); - connPromise = new Promise((resolve) => { - nodeConn.once("connect", () => { - const rid = nodeConn[kHandle][kStreamBaseField].rid; - resolve(rid); - }); - }); - } else { - connPromise = (async () => { - const c = await conn; - return c.rid; - })(); - } - - const session = new ClientHttp2Session(connPromise, url, options); - if (nodeConn) { - nodeConn[kSession] = session; - } session[kAuthority] = `${options.servername || host}:${port}`; session[kProtocol] = protocol; From c80c79fa7e3017ca1d3b932063bbd58cc13723b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 7 Oct 2023 01:17:43 +0200 Subject: [PATCH 08/15] reset CI From 4aae017acdaa8726457a9db5128b4cdd626575b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 7 Oct 2023 01:26:03 +0200 Subject: [PATCH 09/15] lint --- ext/node/polyfills/http2.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts index 1e53a079504b6b..c70ce7a7aee84d 100644 --- a/ext/node/polyfills/http2.ts +++ b/ext/node/polyfills/http2.ts @@ -43,8 +43,6 @@ import { ERR_SOCKET_CLOSED, } from "ext:deno_node/internal/errors.ts"; import { _checkIsHttpToken } from "ext:deno_node/_http_common.ts"; -import { TcpConn } from "ext:deno_net/01_net.js"; -import { TlsConn } from "ext:deno_net/02_tls.js"; const { op_http2_connect, From 2dbe0c209423a9fc0869020b7f68c64550ba45b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 9 Oct 2023 00:33:05 +0200 Subject: [PATCH 10/15] Trying to debug resource in use --- Cargo.lock | 10 ++-------- Cargo.toml | 3 +++ ext/net/ops_tls.rs | 2 ++ ext/net/raw.rs | 12 ++++++++---- ext/node/polyfills/http2.ts | 6 ++++++ 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19964e9dcc56e7..f4825b44a3cc67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1206,8 +1206,6 @@ dependencies = [ [[package]] name = "deno_core" version = "0.221.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf694d66a55050bdfad2036765f93547392ba7c131adc7fcd5e91fa31e291c51" dependencies = [ "anyhow", "bytes", @@ -1595,8 +1593,6 @@ dependencies = [ [[package]] name = "deno_ops" version = "0.97.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aab46b645231decaf3b03d9eb99a5207e4e66c8c53b7774000b44e3ae43e0f99" dependencies = [ "deno-proc-macro-rules", "lazy-regex", @@ -4822,8 +4818,6 @@ dependencies = [ [[package]] name = "serde_v8" version = "0.130.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6078fc5e16615b092b26199e7f1d7d2d336848623d32e00f95e74618274111" dependencies = [ "bytes", "derive_more", @@ -6301,9 +6295,9 @@ dependencies = [ [[package]] name = "v8" -version = "0.79.1" +version = "0.79.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b87d5248d1a7e321a264d21dc7839675fc0bb456e489102272a55b44047869f0" +checksum = "b15561535230812a1db89a696f1f16a12ae6c2c370c6b2241c68d4cb33963faf" dependencies = [ "bitflags 1.3.2", "fslock", diff --git a/Cargo.toml b/Cargo.toml index 3720eb74f267f3..866050cc1be43e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -331,3 +331,6 @@ opt-level = 3 opt-level = 3 [profile.release.package.base64-simd] opt-level = 3 + +[patch.crates-io] +deno_core = { path = "../deno_core/core" } diff --git a/ext/net/ops_tls.rs b/ext/net/ops_tls.rs index fe0e70a5cf0af1..bff51087cd7195 100644 --- a/ext/net/ops_tls.rs +++ b/ext/net/ops_tls.rs @@ -1152,9 +1152,11 @@ pub async fn op_tls_handshake( state: Rc>, #[smi] rid: ResourceId, ) -> Result { + eprintln!("handshake started"); let resource = state .borrow() .resource_table .get::(rid)?; + eprintln!("handshake finished"); resource.handshake().await } diff --git a/ext/net/raw.rs b/ext/net/raw.rs index 0c92c46707df16..cebe3281203075 100644 --- a/ext/net/raw.rs +++ b/ext/net/raw.rs @@ -229,8 +229,10 @@ pub fn take_network_stream_resource( if let Ok(resource_rc) = resource_table.take::(stream_rid) { // This TCP connection might be used somewhere else. - let resource = Rc::try_unwrap(resource_rc) - .map_err(|_| bad_resource("TCP stream is currently in use"))?; + let resource = Rc::try_unwrap(resource_rc).map_err(|_| { + panic!(); + bad_resource("TCP stream is currently in use") + })?; let (read_half, write_half) = resource.into_inner(); let tcp_stream = read_half.reunite(write_half)?; return Ok(NetworkStream::Tcp(tcp_stream)); @@ -239,8 +241,10 @@ pub fn take_network_stream_resource( if let Ok(resource_rc) = resource_table.take::(stream_rid) { // This TLS connection might be used somewhere else. - let resource = Rc::try_unwrap(resource_rc) - .map_err(|_| bad_resource("TLS stream is currently in use"))?; + let resource = Rc::try_unwrap(resource_rc).map_err(|_| { + panic!(); + bad_resource("TLS stream is currently in use") + })?; let (read_half, write_half) = resource.into_inner(); let tls_stream = read_half.reunite(write_half); return Ok(NetworkStream::Tls(tls_stream)); diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts index c70ce7a7aee84d..16820dbf877506 100644 --- a/ext/node/polyfills/http2.ts +++ b/ext/node/polyfills/http2.ts @@ -372,6 +372,12 @@ export class ClientHttp2Session extends Http2Session { const eventName = url.startsWith("https") ? "secureConnect" : "connect"; console.log("eventName", eventName, url); socket.once(eventName, () => { + console.log( + "emitted", + eventName, + socket[kHandle][kStreamBaseField].rid, + ); + console.table(Deno.resources()); const rid = socket[kHandle][kStreamBaseField].rid; resolve(rid); }); From 962333e519412995062c4fa5cda8727db09817fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 9 Oct 2023 21:02:15 +0200 Subject: [PATCH 11/15] debug --- ext/node/polyfills/http2.ts | 8 +++++--- ext/node/polyfills/net.ts | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts index 16820dbf877506..cb631f179c797a 100644 --- a/ext/node/polyfills/http2.ts +++ b/ext/node/polyfills/http2.ts @@ -379,7 +379,9 @@ export class ClientHttp2Session extends Http2Session { ); console.table(Deno.resources()); const rid = socket[kHandle][kStreamBaseField].rid; - resolve(rid); + nextTick(() => { + resolve(rid); + }); }); }); socket[kSession] = this; @@ -1554,12 +1556,12 @@ export function connect( switch (protocol) { case "http:": url = `http://${host}${port == 80 ? "" : (":" + port)}`; - socket = netConnect({ port, host, ...options }); + socket = netConnect({ port, host, ...options, pauseOnCreate: true }); break; case "https:": // TODO(bartlomieju): handle `initializeTLSOptions` here url = `https://${host}${port == 443 ? "" : (":" + port)}`; - socket = tlsConnect(port, host, {}); + socket = tlsConnect(port, host, { manualStart: true }); break; default: throw new ERR_HTTP2_UNSUPPORTED_PROTOCOL(protocol); diff --git a/ext/node/polyfills/net.ts b/ext/node/polyfills/net.ts index bcc982d3bfa858..666aed9660ee7e 100644 --- a/ext/node/polyfills/net.ts +++ b/ext/node/polyfills/net.ts @@ -347,6 +347,7 @@ function _afterConnect( socket._sockname = null; if (status === 0) { + console.log("afterConnect", socket.readable, !readable, socket.isPaused()); if (socket.readable && !readable) { socket.push(null); socket.read(); @@ -824,6 +825,7 @@ export class Socket extends Duplex { _initSocketHandle(this); + console.log("net.Socket", options.manualStart, options); // If we have a handle, then start the flow of data into the // buffer. If not, then this will happen when we connect. if (this._handle && options.readable !== false) { From 70a5e6cd3f9b73f1bbda019c60a564958f299818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 10 Oct 2023 13:48:01 +0200 Subject: [PATCH 12/15] call pause on socket --- ext/node/polyfills/http2.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts index cb631f179c797a..d2e4282f343052 100644 --- a/ext/node/polyfills/http2.ts +++ b/ext/node/polyfills/http2.ts @@ -366,6 +366,7 @@ export class ClientHttp2Session extends Http2Session { this[kDenoConnRid] = undefined; this[kPollConnPromiseId] = undefined; + socket.pause(); socket.on("error", socketOnError); socket.on("close", socketOnClose); const connPromise = new Promise((resolve) => { From e8c7be17150385649ae6d43f7c7e8c8a9078729f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 11 Oct 2023 01:11:32 +0200 Subject: [PATCH 13/15] revert unrelated changes --- Cargo.toml | 3 --- ext/net/ops_tls.rs | 2 -- ext/net/raw.rs | 12 ++++-------- ext/node/polyfills/net.ts | 2 -- 4 files changed, 4 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 866050cc1be43e..3720eb74f267f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -331,6 +331,3 @@ opt-level = 3 opt-level = 3 [profile.release.package.base64-simd] opt-level = 3 - -[patch.crates-io] -deno_core = { path = "../deno_core/core" } diff --git a/ext/net/ops_tls.rs b/ext/net/ops_tls.rs index bff51087cd7195..fe0e70a5cf0af1 100644 --- a/ext/net/ops_tls.rs +++ b/ext/net/ops_tls.rs @@ -1152,11 +1152,9 @@ pub async fn op_tls_handshake( state: Rc>, #[smi] rid: ResourceId, ) -> Result { - eprintln!("handshake started"); let resource = state .borrow() .resource_table .get::(rid)?; - eprintln!("handshake finished"); resource.handshake().await } diff --git a/ext/net/raw.rs b/ext/net/raw.rs index cebe3281203075..0c92c46707df16 100644 --- a/ext/net/raw.rs +++ b/ext/net/raw.rs @@ -229,10 +229,8 @@ pub fn take_network_stream_resource( if let Ok(resource_rc) = resource_table.take::(stream_rid) { // This TCP connection might be used somewhere else. - let resource = Rc::try_unwrap(resource_rc).map_err(|_| { - panic!(); - bad_resource("TCP stream is currently in use") - })?; + let resource = Rc::try_unwrap(resource_rc) + .map_err(|_| bad_resource("TCP stream is currently in use"))?; let (read_half, write_half) = resource.into_inner(); let tcp_stream = read_half.reunite(write_half)?; return Ok(NetworkStream::Tcp(tcp_stream)); @@ -241,10 +239,8 @@ pub fn take_network_stream_resource( if let Ok(resource_rc) = resource_table.take::(stream_rid) { // This TLS connection might be used somewhere else. - let resource = Rc::try_unwrap(resource_rc).map_err(|_| { - panic!(); - bad_resource("TLS stream is currently in use") - })?; + let resource = Rc::try_unwrap(resource_rc) + .map_err(|_| bad_resource("TLS stream is currently in use"))?; let (read_half, write_half) = resource.into_inner(); let tls_stream = read_half.reunite(write_half); return Ok(NetworkStream::Tls(tls_stream)); diff --git a/ext/node/polyfills/net.ts b/ext/node/polyfills/net.ts index 666aed9660ee7e..bcc982d3bfa858 100644 --- a/ext/node/polyfills/net.ts +++ b/ext/node/polyfills/net.ts @@ -347,7 +347,6 @@ function _afterConnect( socket._sockname = null; if (status === 0) { - console.log("afterConnect", socket.readable, !readable, socket.isPaused()); if (socket.readable && !readable) { socket.push(null); socket.read(); @@ -825,7 +824,6 @@ export class Socket extends Duplex { _initSocketHandle(this); - console.log("net.Socket", options.manualStart, options); // If we have a handle, then start the flow of data into the // buffer. If not, then this will happen when we connect. if (this._handle && options.readable !== false) { From 2f196c47a2fbd5f4b3b3e396009fd94d03b7e5c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 11 Oct 2023 01:18:06 +0200 Subject: [PATCH 14/15] pause socket --- Cargo.lock | 10 ++++++++-- ext/node/polyfills/http2.ts | 4 +++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4825b44a3cc67..a2852311c49083 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1206,6 +1206,8 @@ dependencies = [ [[package]] name = "deno_core" version = "0.221.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf694d66a55050bdfad2036765f93547392ba7c131adc7fcd5e91fa31e291c51" dependencies = [ "anyhow", "bytes", @@ -1593,6 +1595,8 @@ dependencies = [ [[package]] name = "deno_ops" version = "0.97.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aab46b645231decaf3b03d9eb99a5207e4e66c8c53b7774000b44e3ae43e0f99" dependencies = [ "deno-proc-macro-rules", "lazy-regex", @@ -4818,6 +4822,8 @@ dependencies = [ [[package]] name = "serde_v8" version = "0.130.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6078fc5e16615b092b26199e7f1d7d2d336848623d32e00f95e74618274111" dependencies = [ "bytes", "derive_more", @@ -6103,8 +6109,8 @@ version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ - "cfg-if 0.1.10", - "rand 0.7.3", + "cfg-if 1.0.0", + "rand 0.8.5", "static_assertions", ] diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts index d2e4282f343052..6dcdde66f4cafd 100644 --- a/ext/node/polyfills/http2.ts +++ b/ext/node/polyfills/http2.ts @@ -366,7 +366,6 @@ export class ClientHttp2Session extends Http2Session { this[kDenoConnRid] = undefined; this[kPollConnPromiseId] = undefined; - socket.pause(); socket.on("error", socketOnError); socket.on("close", socketOnClose); const connPromise = new Promise((resolve) => { @@ -1569,6 +1568,9 @@ export function connect( } } + // Pause so no "socket.read()" starts in the background that would + // prevent us from taking ownership of the socket in `ClientHttp2Session` + socket.pause(); const session = new ClientHttp2Session(socket, url, options); session[kAuthority] = `${options.servername || host}:${port}`; From 936394b72d9a0f19e6b608a91921115158485f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 12 Oct 2023 15:05:37 +0200 Subject: [PATCH 15/15] cleanup, add a test for createConnection --- cli/tests/unit_node/http2_test.ts | 46 +++++++++++++++++++++++++++++++ ext/node/polyfills/http2.ts | 7 ----- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/cli/tests/unit_node/http2_test.ts b/cli/tests/unit_node/http2_test.ts index 8e7b261ae5c13c..d0e0de43f1e90c 100644 --- a/cli/tests/unit_node/http2_test.ts +++ b/cli/tests/unit_node/http2_test.ts @@ -63,6 +63,52 @@ for (const url of ["http://127.0.0.1:4246", "https://127.0.0.1:4247"]) { }); } +Deno.test(`[node/http2 client createConnection]`, { + ignore: Deno.build.os === "windows", +}, async () => { + const url = "http://127.0.0.1:4246"; + const createConnPromise = deferred(); + // Create a server to respond to the HTTP2 requests + const client = http2.connect(url, { + createConnection() { + const socket = net.connect({ host: "127.0.0.1", port: 4246 }); + + socket.on("connect", () => { + createConnPromise.resolve(); + }); + + return socket; + }, + }); + client.on("error", (err) => console.error(err)); + + const req = client.request({ ":method": "POST", ":path": "/" }); + + let receivedData = ""; + + req.write("hello"); + req.setEncoding("utf8"); + + req.on("data", (chunk) => { + receivedData += chunk; + }); + req.end(); + + const endPromise = deferred(); + setTimeout(() => { + try { + client.close(); + } catch (_) { + // pass + } + endPromise.resolve(); + }, 2000); + + await createConnPromise; + await endPromise; + assertEquals(receivedData, "hello world\n"); +}); + // TODO(bartlomieju): reenable sanitizers Deno.test("[node/http2 server]", { sanitizeOps: false }, async () => { const server = http2.createServer(); diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts index 6dcdde66f4cafd..9ebdabb7955507 100644 --- a/ext/node/polyfills/http2.ts +++ b/ext/node/polyfills/http2.ts @@ -370,14 +370,7 @@ export class ClientHttp2Session extends Http2Session { socket.on("close", socketOnClose); const connPromise = new Promise((resolve) => { const eventName = url.startsWith("https") ? "secureConnect" : "connect"; - console.log("eventName", eventName, url); socket.once(eventName, () => { - console.log( - "emitted", - eventName, - socket[kHandle][kStreamBaseField].rid, - ); - console.table(Deno.resources()); const rid = socket[kHandle][kStreamBaseField].rid; nextTick(() => { resolve(rid);