diff --git a/packages/connect-node/.npmignore b/packages/connect-node/.npmignore index c88463abd..79c72b325 100644 --- a/packages/connect-node/.npmignore +++ b/packages/connect-node/.npmignore @@ -2,3 +2,4 @@ /*.json /dist/*/**/*.spec.js /dist/*/**/*.spec.d.ts +/dist/*/testdata/ diff --git a/packages/connect-node/src/http2-session-manager.spec.ts b/packages/connect-node/src/http2-session-manager.spec.ts index 71dcd0c95..c196968ed 100644 --- a/packages/connect-node/src/http2-session-manager.spec.ts +++ b/packages/connect-node/src/http2-session-manager.spec.ts @@ -16,6 +16,7 @@ import { useNodeServer } from "./use-node-server-helper.spec.js"; import * as http2 from "http2"; import { Http2SessionManager } from "./http2-session-manager.js"; import { ConnectError } from "@connectrpc/connect"; +import { Worker } from "worker_threads"; describe("Http2SessionManager", function () { const serverSessions: http2.ServerHttp2Session[] = []; @@ -167,6 +168,24 @@ describe("Http2SessionManager", function () { sm.abort(); expect(sm.state()).toBe("closed"); }); + it("verify should keep the process alive", async function () { + const worker = new Worker( + "./dist/cjs/testdata/http2-session-manager-verify-ping.js", + { + workerData: server.getUrl(), + }, + ); + worker.once("error", (err) => { + fail(err); + }); + await expectAsync( + new Promise((resolve) => { + worker.once("message", (message) => { + resolve(message as string); + }); + }), + ).toBeResolvedTo("done"); + }); it("should open a new connection if verification for the old one fails", async function () { const sm = new Http2SessionManager(server.getUrl(), { pingTimeoutMs: 0, // intentionally unsatisfiable diff --git a/packages/connect-node/src/http2-session-manager.ts b/packages/connect-node/src/http2-session-manager.ts index 545510251..834b189a3 100644 --- a/packages/connect-node/src/http2-session-manager.ts +++ b/packages/connect-node/src/http2-session-manager.ts @@ -647,8 +647,12 @@ function ready( resetPingInterval(); }, ping() { + conn.ref(); return new Promise((resolve) => { - commonPing(() => resolve(true)); + commonPing(() => { + if (streamCount == 0) conn.unref(); + resolve(true); + }); conn.once("error", () => resolve(false)); }); }, diff --git a/packages/connect-node/src/testdata/http2-session-manager-verify-ping.ts b/packages/connect-node/src/testdata/http2-session-manager-verify-ping.ts new file mode 100644 index 000000000..c826bbf3b --- /dev/null +++ b/packages/connect-node/src/testdata/http2-session-manager-verify-ping.ts @@ -0,0 +1,34 @@ +// Copyright 2021-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/* eslint-disable @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-floating-promises */ + +import { Http2SessionManager } from "../http2-session-manager.js"; +import * as http2 from "http2"; +import { parentPort, workerData } from "worker_threads"; + +const sm = new Http2SessionManager(workerData, { + pingIntervalMs: 5, // intentionally short for faster tests +}); +sm.request("POST", "/", {}, {}).then((req) => { + req.close(http2.constants.NGHTTP2_NO_ERROR, () => + setTimeout(() => { + sm.request("POST", "/", {}, {}).then((req) => { + req.close(http2.constants.NGHTTP2_NO_ERROR, () => { + parentPort!.postMessage("done"); + }); + }); + }, 10), + ); +}); diff --git a/packages/connect-node/tsconfig.json b/packages/connect-node/tsconfig.json index 31e997390..923e771a3 100644 --- a/packages/connect-node/tsconfig.json +++ b/packages/connect-node/tsconfig.json @@ -1,5 +1,5 @@ { "files": ["src/index.ts"], - "include": ["src/**/*.spec.ts"], + "include": ["src/**/*.spec.ts", "src/testdata/*.ts"], "extends": "../../tsconfig.base.json" }