Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix get-port #52

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 54 additions & 11 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';
const net = require('net');
const os = require('os');

class Locked extends Error {
constructor(port) {
Expand All @@ -20,17 +21,58 @@ const releaseOldLockedPortsIntervalMs = 1000 * 15;
// Lazily create interval on first use
let interval;

const getAvailablePort = options => new Promise((resolve, reject) => {
const server = net.createServer();
server.unref();
server.on('error', reject);
server.listen(options, () => {
const {port} = server.address();
server.close(() => {
resolve(port);
const getHosts = () => {
const interfaces = os.networkInterfaces();
const results = [];

for (const _interface of Object.values(interfaces)) {
for (const config of _interface) {
results.push(config.address);
}
}

// Add undefined value, for createServer function, do not use host and default 0.0.0.0 host
return results.concat(undefined, '0.0.0.0');
};

const getAvailablePort = options =>
new Promise((resolve, reject) => {
const server = net.createServer();
server.unref();
server.on('error', reject);
server.listen(options, () => {
const {port} = server.address();
server.close(() => {
resolve(port);
});
});
});
});

const testPortForHosts = async (options, hosts) => {
if (options.host || options.port === 0) {
return getAvailablePort(options);
}

let index = 0;

while (index < hosts.length) {
const host = hosts[index];
try {
await getAvailablePort({port: options.port, host}); // eslint-disable-line no-await-in-loop
} catch (error) {
if (['EADDRNOTAVAIL', 'EINVAL'].includes(error.code)) {
hosts.splice(hosts.indexOf(host), 1);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mutates the passed-in array, which can cause problems later on. Copy the array at the top of this function before using it.

continue;
} else {
throw error;
}
}

++index;
}

return options.port;
};

const portCheckSequence = function * (ports) {
if (ports) {
Expand Down Expand Up @@ -59,15 +101,16 @@ module.exports = async options => {
}
}

const hosts = getHosts();
for (const port of portCheckSequence(ports)) {
try {
let availablePort = await getAvailablePort({...options, port}); // eslint-disable-line no-await-in-loop
let availablePort = await testPortForHosts({...options, port}, hosts); // eslint-disable-line no-await-in-loop
while (lockedPorts.old.has(availablePort) || lockedPorts.young.has(availablePort)) {
if (port !== 0) {
throw new Locked(port);
}

availablePort = await getAvailablePort({...options, port}); // eslint-disable-line no-await-in-loop
availablePort = await testPortForHosts({...options, port}, hosts); // eslint-disable-line no-await-in-loop
}

lockedPorts.young.add(availablePort);
Expand Down
17 changes: 17 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,20 @@ test('ports are locked for up to 30 seconds', async t => {
t.is(port3, port);
global.setInterval = setInterval;
});

const boundPort = async ({port, host}) => {
const server = net.createServer();
await promisify(server.listen.bind(server))({port, host});
return server;
};

test('preferred ports is bound up with different hosts', async t => {
const desiredPorts = [10990, 10991, 10992];

await boundPort({port: desiredPorts[0], host: '::'});
await boundPort({port: desiredPorts[1], host: '127.0.0.1'});

const port = await getPort({port: desiredPorts});

t.is(port, desiredPorts[2]);
});
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have liked to see more comprehensive testing than a single assertion.