denoland / deno

A modern runtime for JavaScript and TypeScript.
https://deno.com
MIT License
95.43k stars 5.3k forks source link

node: Error when using `npm:portfinder` #18301

Closed ayame113 closed 1 month ago

ayame113 commented 1 year ago

I'm having trouble running portfinder, a package that finds available ports, on Deno.

Running the code below in Node.js correctly finds available ports.

import { getPortPromise } from "npm:portfinder";
// import { getPortPromise } from "portfinder";

console.log(await getPortPromise());

However, when I run it on Deno, I get the following error:

> deno run -A ./tmp.mjs
error: Uncaught Error: No open ports available
    at Server.onError (file:///C:/Users/ayame/AppData/Local/deno/npm/registry.npmjs.org/portfinder/1.0.32/lib/portfinder.js:58:23)
    at Object.onceWrapper (https://deno.land/std@0.168.0/node/_events.mjs:504:26)
    at Server.emit (https://deno.land/std@0.168.0/node/_events.mjs:379:28)
    at _emitErrorNT (https://deno.land/std@0.168.0/node/net.ts:1806:10)
    at processTicksAndRejections (https://deno.land/std@0.168.0/node/_next_tick.ts:41:15)

This problem should affect many packages that depend on portfinder.

> deno --version
deno 1.29.0 (release, x86_64-pc-windows-msvc)
v8 10.9.194.5
typescript 4.9.4
ayame113 commented 1 year ago

I tried debugging this.

This package first checks port availability in order for all hostnames returned by networkInterfaces(). At that time, the server created by net.createServer will be reused. The result is an EADDRINUSE error because it's listening as soon as it's closed (this is the behavior difference between Node.js and Deno). This seems to make the portfinder package determine that the port is not available.

So the minimal code that demonstrates the difference in behavior between Node.js and Deno looks like this:

// import net from "https://deno.land/std@0.168.0/node/net.ts";
import net from "node:net";

const host1 = "0.0.0.0";
const host2 = null;

const server = net.createServer(() => {});

server.once("listening", () => {
  // Listen on another host as soon as close the server.
  server.close();
  server.listen(8081, host2); // EADDRINUSE error occurs only in Deno!
});

server.listen(8081, host1);
kt3k commented 1 year ago

Currently it's difficult to make server.close() synchronous because the close() method of flash (Deno.serve) is asynchronous.

~~flash is now under a heavy rewrite. ref. https://github.com/denoland/deno/issues/17146 Maybe we can start considering adding synchronous shutting down API to it after the rewrite completed.~~

Update: Deno.serve is unrelated to this issue. portfinder uses net.Server, which is shimmed by Deno.listen and Deno.Listener.

kt3k commented 1 year ago

Now the error message is like the below:

error: Uncaught Error: listen UNKNOWN: unknown error 192.168.133.163:32561
    at __node_internal_captureLargerStackTrace (ext:deno_node/internal/errors.ts:89:11)
    at __node_internal_uvExceptionWithHostPort (ext:deno_node/internal/errors.ts:120:12)
    at Server._setupListenHandle [as _listen2] (ext:deno_node/net.ts:1187:20)
    at _listenInCluster (ext:deno_node/net.ts:1009:16)
    at doListen (ext:deno_node/net.ts:1019:13)
    at processTicksAndRejections (ext:deno_node/_next_tick.ts:34:29)
felipecrs commented 1 year ago

Just to mention this is probably the reason why npm:http-server also does not work unless --port is specified.

mmastrac commented 9 months ago

Will fix as part of https://github.com/denoland/deno_core/pull/440