Skip to content

Commit 2a930df

Browse files
feat(ext/node): notify control socket when node:http server starts serving (#34949)
Upstream PR #34662 landed the `DENO_SERVE_ADDRESS` override for `node:http`/`node:https`, and PR #34676 wired `Deno.serve()` to call `op_http_notify_serving()` so that `DENO_AUTO_SERVE` setups can wait for a "Serving" event over `DENO_UNSTABLE_CONTROL_SOCK`. Node servers were left out: they observed the override address but never fired the notification, so a control plane waiting on the socket would hang. This hooks `notifyAddressOverrideServing()` into the `listen()` path of both `http.Server` and `https.Server` (and into `startOverrideListener` for non-TCP override transports) so `node:http` servers fire the same notification that `Deno.serve()` does. The unconfigured spec gains a `node:http` variant exercising the full `DENO_AUTO_SERVE` + `DENO_SERVE_ADDRESS=duplicate,unix:...` + `DENO_UNSTABLE_CONTROL_SOCK` flow. Co-authored-by: Avocet <avocet@deno.com>
1 parent 1472e69 commit 2a930df

5 files changed

Lines changed: 91 additions & 3 deletions

File tree

ext/node/polyfills/_http_server.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ const { enqueueNodePerformanceEntry, hasNodeObserverForType } = core
111111
);
112112
import {
113113
applyAddressOverride,
114+
notifyAddressOverrideServing,
114115
startOverrideListener,
115116
} from "ext:deno_node/internal/http/address_override.js";
116117
const {
@@ -1446,6 +1447,7 @@ Server.prototype.listen = function listen(...args) {
14461447
}
14471448
const rewritten = [{ host: applied.host, port: applied.port }];
14481449
if (cb) ArrayPrototypePush(rewritten, cb);
1450+
this.once("listening", notifyAddressOverrideServing);
14491451
return FunctionPrototypeApply(
14501452
net.Server.prototype.listen,
14511453
this,

ext/node/polyfills/https.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ const { Buffer } = core.loadExtScript("ext:deno_node/internal/buffer.mjs");
6060
const tls = lazyTls().default;
6161
const net = lazyNet();
6262
const http = lazyHttp();
63-
const { applyAddressOverride, startOverrideListener } = lazyAddressOverride();
63+
const {
64+
applyAddressOverride,
65+
notifyAddressOverrideServing,
66+
startOverrideListener,
67+
} = lazyAddressOverride();
6468
const { _connectionListener, ClientRequest, ServerImpl: HttpServer } = http;
6569

6670
function getExtraCACertificates() {
@@ -151,6 +155,7 @@ Server.prototype.listen = function listen(this: any, ...args: any[]) {
151155
}
152156
const rewritten: any[] = [{ host: applied.host, port: applied.port }];
153157
if (cb) ArrayPrototypePush(rewritten, cb);
158+
this.once("listening", notifyAddressOverrideServing);
154159
return FunctionPrototypeApply(
155160
net.Server.prototype.listen,
156161
this,

ext/node/polyfills/internal/http/address_override.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
// Duplex wrapper around the Deno.Conn.
1818

1919
import { core, primordials } from "ext:core/mod.js";
20-
import { op_http_serve_address_override } from "ext:core/ops";
20+
import {
21+
op_http_notify_serving,
22+
op_http_serve_address_override,
23+
} from "ext:core/ops";
2124

2225
import { Buffer } from "node:buffer";
2326
import { Duplex } from "node:stream";
@@ -61,6 +64,10 @@ function consumeOverride() {
6164
addressOverrideConsumed = true;
6265
}
6366

67+
function notifyAddressOverrideServing() {
68+
op_http_notify_serving();
69+
}
70+
6471
// Translate an override record into the argument for denoListen().
6572
function overrideToListenArgs(override) {
6673
switch (override.kind) {
@@ -271,6 +278,7 @@ function startOverrideListener(server, override, connectionListener) {
271278
}
272279

273280
server[kOverrideListener] = denoListener;
281+
notifyAddressOverrideServing();
274282

275283
(async () => {
276284
try {
@@ -337,4 +345,8 @@ function applyAddressOverride() {
337345
return { mode: "override-only", override };
338346
}
339347

340-
export { applyAddressOverride, startOverrideListener };
348+
export {
349+
applyAddressOverride,
350+
notifyAddressOverrideServing,
351+
startOverrideListener,
352+
};

tests/specs/run/unconfigured/main.out

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ hello world
33
EVENT: "Serving"
44

55
[Object: null prototype] { success: true, code: 0, signal: null }
6+
node listening
7+
NODE EVENT: "Serving"
8+
9+
[Object: null prototype] { success: true, code: 0, signal: null }

tests/specs/run/unconfigured/main.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,68 @@ const n = await sock.read(buf);
5454
console.log("EVENT:", new TextDecoder().decode(buf.subarray(0, n)));
5555

5656
console.log(await child.status);
57+
58+
const nodeSockPath = `${tempDirPath}/node-control.sock`;
59+
const nodeOverrideSockPath = `${tempDirPath}/node-override.sock`;
60+
const nodeTestPath = `${tempDirPath}/node_test.ts`;
61+
62+
const nodeCommand = new Deno.Command(Deno.execPath(), {
63+
env: {
64+
DENO_UNSTABLE_CONTROL_SOCK: `unix:${nodeSockPath}`,
65+
},
66+
});
67+
68+
const nodeChild = nodeCommand.spawn();
69+
70+
i = 0;
71+
while (true) {
72+
try {
73+
await Deno.lstat(nodeSockPath);
74+
break;
75+
} catch {}
76+
77+
i += 1;
78+
if (i > 100) {
79+
throw new Error(`${nodeSockPath} did not exist`);
80+
}
81+
82+
await new Promise((r) => setTimeout(r, 10));
83+
}
84+
85+
const nodeSock = await Deno.connect({
86+
transport: "unix",
87+
path: nodeSockPath,
88+
});
89+
90+
Deno.writeTextFileSync(
91+
nodeTestPath,
92+
`
93+
import http from "node:http";
94+
const server = http.createServer((_req, res) => res.end("ok"));
95+
server.listen(0, () => {
96+
console.log("node listening");
97+
server.close();
98+
});
99+
`,
100+
);
101+
102+
const nodeData = JSON.stringify({
103+
cwd: tempDirPath,
104+
args: ["run", "-A", "node_test.ts"],
105+
env: [
106+
["DENO_AUTO_SERVE", "1"],
107+
["DENO_SERVE_ADDRESS", `duplicate,unix:${nodeOverrideSockPath}`],
108+
],
109+
});
110+
111+
await nodeSock.write(new TextEncoder().encode(nodeData + "\n"));
112+
113+
const nodeBuf = new Uint8Array(128);
114+
const nodeN = await nodeSock.read(nodeBuf);
115+
116+
console.log(
117+
"NODE EVENT:",
118+
new TextDecoder().decode(nodeBuf.subarray(0, nodeN)),
119+
);
120+
121+
console.log(await nodeChild.status);

0 commit comments

Comments
 (0)