Skip to content

Commit

Permalink
feat(getport): use net0listen by default
Browse files Browse the repository at this point in the history
fixes #827
  • Loading branch information
hasezoey committed Jun 8, 2024
1 parent ab7a73c commit 887f1a1
Show file tree
Hide file tree
Showing 5 changed files with 25 additions and 77 deletions.
14 changes: 0 additions & 14 deletions docs/api/config-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,20 +269,6 @@ Also see [ARCHIVE_NAME](#archive_name).
Keep in mind that downloaded binaries will never be automatically deleted.
:::

### EXP_NET0LISTEN

| Environment Variable | PackageJson |
| :------------------: | :---------: |
| `MONGOMS_EXP_NET0LISTEN` | `expNet0Listen` |

Option `EXP_NET0LISTEN` is used to use the experimental (non-predictable) port generation of `net.listen`.

This option will use a random open port, which will lessen the "port is already in use" errors, but will not eliminate them.

This is a experimental option, it maybe removed, renamed or have changed behavior in the future.

Default: `false`

## How to use them in the package.json

To use the config options in the `package.json`, they need to be camelCased (and without `_`), and need to be in the property `config.mongodbMemoryServer`
Expand Down
5 changes: 5 additions & 0 deletions docs/guides/migration/migrate10.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@ It is recommended to run the tests against a tmpfs or equivalent (default `/tmp`

The tsconfig `target` option has been updated to `es2021`, which will result in less polyfills.
This should be a non-breaking change.

### Getport now uses `net0listen` by default

This means a port is now generated by the engine (like nodejs) itself, previously known as `EXP_NET0LISTEN`.
This should reduce amount of `Max port tries exceeded` errors.
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import resolveConfig, {
ResolveConfigVariables,
defaultValues,
envToBool,
setDefaultValue,
} from '../../resolveConfig';
import { defaultValues } from '../../resolveConfig';
import * as getPort from '../index';
import * as net from 'node:net';

Expand Down Expand Up @@ -71,29 +66,8 @@ describe('getport', () => {
await expect(getPort.getFreePort(testPort)).resolves.toStrictEqual(testPort);
});

it('port should be predictable', async () => {
expect(envToBool(resolveConfig(ResolveConfigVariables.EXP_NET0LISTEN))).toStrictEqual(false);

const testPort = 23232;
await expect(getPort.getFreePort(testPort)).resolves.toStrictEqual(testPort);

const server = await new Promise<net.Server>((res) => {
const server = net.createServer();
server.unref();
server.listen(testPort, () => res(server));
});

const foundPort = await getPort.getFreePort(testPort);
expect(foundPort).toStrictEqual(testPort + 2); // predictable result

server.close();
});

it('EXP_NET0LISTEN should not be predictable', async () => {
setDefaultValue(ResolveConfigVariables.EXP_NET0LISTEN, 'true');
expect(envToBool(resolveConfig(ResolveConfigVariables.EXP_NET0LISTEN))).toStrictEqual(true);

const testPort = 23232;
it('port should not be predictable (net0listen)', async () => {
const testPort = 23233;
await expect(getPort.getFreePort(testPort)).resolves.toStrictEqual(testPort);

const server = await new Promise<net.Server>((res) => {
Expand Down
50 changes: 17 additions & 33 deletions packages/mongodb-memory-server-core/src/util/getport/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import resolveConfig, { ResolveConfigVariables, envToBool } from '../resolveConfig';
import * as crypto from 'node:crypto';
import * as net from 'node:net';
import debug from 'debug';

Expand All @@ -16,8 +14,6 @@ interface IPortsCache {
timeSet: undefined | number;
/** The ports that were tried */
ports: Set<number>;
/** Store last used number, reduces amount of tries needed */
lastNumber: number;
}

/**
Expand All @@ -32,15 +28,14 @@ const PORTS_CACHE_CLEAN_TIME = 1000 * 10;
const PORTS_CACHE: IPortsCache = {
timeSet: undefined,
ports: new Set(),
lastNumber: MIN_PORT,
};

/** Max default tries before giving up */
const MAX_DEFAULT_TRIES = 10;

/**
* Try to get a free port
* @param firstPort The first port to try or empty for semi-random port
* Try to get a free port.
* @param firstPort The first port to try or empty for a random port
* @param max_tries maximum amount of tries to get a port, default to {@link MAX_DEFAULT_TRIES}
* @returns A valid free port
* @throws if "max_tries" is exceeded
Expand All @@ -49,8 +44,8 @@ export async function getFreePort(
firstPort?: number,
max_tries: number = MAX_DEFAULT_TRIES
): Promise<number> {
// Get a random value from crypto to use as first port if none is given
firstPort = firstPort || validPort(crypto.randomInt(MIN_PORT, MAX_PORT + 1));
// use "0" as a fallback to use net0listen, which generates a random free port
firstPort = firstPort || 0;

// clear ports cache after some time, but not on an interval
if (PORTS_CACHE.timeSet && Date.now() - PORTS_CACHE.timeSet > PORTS_CACHE_CLEAN_TIME) {
Expand All @@ -60,22 +55,12 @@ export async function getFreePort(
PORTS_CACHE.timeSet = Date.now();
}

const exp_net0listen = envToBool(resolveConfig(ResolveConfigVariables.EXP_NET0LISTEN));
log('EXP_NET0LISTEN', exp_net0listen);

let tries = 0;
while (tries <= max_tries) {
tries += 1;

let nextPort: number;

if (exp_net0listen) {
// "0" means to use ".listen" random port
nextPort = tries === 1 ? firstPort : 0;
} else {
// use "startPort" at first try, otherwise increase from last number
nextPort = tries === 1 ? firstPort : validPort(PORTS_CACHE.lastNumber + tries);
}
// "0" means to use have ".listen" use a random port
const nextPort = tries === 1 ? firstPort : 0;

// try next port, because it is already in the cache
// unless port is "0" which will use "net.listen(0)"
Expand All @@ -84,15 +69,15 @@ export async function getFreePort(
}

PORTS_CACHE.ports.add(nextPort);
// only set "lastNumber" if the "nextPort" was not in the cache
PORTS_CACHE.lastNumber = nextPort;

const triedPort = await tryPort(nextPort);

// returned port can be different than the "nextPort" (if EXP_NET0LISTEN)
PORTS_CACHE.ports.add(nextPort);

if (triedPort > 0) {
log('getFreePort: found free port', triedPort);

// returned port can be different than the "nextPort" (if net0listen)
PORTS_CACHE.ports.add(nextPort);

return triedPort;
}
}
Expand All @@ -103,8 +88,8 @@ export async function getFreePort(
export default getFreePort;

/**
* Check that input number is within range of {@link MIN_PORT} and {@link MAX_PORT}
* If more than {@link MAX_PORT}, wrap around, if less than {@link MIN_PORT} use {@link MIN_PORT}
* Ensure that input number is within range of {@link MIN_PORT} and {@link MAX_PORT}.
* If more than {@link MAX_PORT}, wrap around, if less than {@link MIN_PORT} use {@link MIN_PORT}.
* @param port The Number to check
* @returns A Valid number in port range
*/
Expand All @@ -115,9 +100,9 @@ export function validPort(port: number): number {
}

/**
* Try a given port
* Try a given port.
* @param port The port to try
* @returns "true" if the port is not in use, "false" if in use
* @returns the port if successful, "-1" in case of `EADDRINUSE`, all other errors reject
* @throws The error given if the code is not "EADDRINUSE"
*/
export function tryPort(port: number): Promise<number> {
Expand Down Expand Up @@ -147,12 +132,11 @@ export function tryPort(port: number): Promise<number> {
}

/**
* Reset the {@link PORTS_CACHE} to its initial state
* Reset the {@link PORTS_CACHE} to its initial state.
*
* This function is meant for debugging and testing purposes only
* This function is meant for debugging and testing purposes only.
*/
export function resetPortsCache() {
PORTS_CACHE.lastNumber = MIN_PORT;
PORTS_CACHE.timeSet = undefined;
PORTS_CACHE.ports.clear();
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export enum ResolveConfigVariables {
USE_ARCHIVE_NAME_FOR_BINARY_NAME = 'USE_ARCHIVE_NAME_FOR_BINARY_NAME',
MAX_REDIRECTS = 'MAX_REDIRECTS',
DISTRO = 'DISTRO',
EXP_NET0LISTEN = 'EXP_NET0LISTEN',
}

/** The Prefix for Environmental values */
Expand Down

0 comments on commit 887f1a1

Please sign in to comment.