From 57c443b401cbf92f3dd0198b5b640b23009abbd7 Mon Sep 17 00:00:00 2001 From: David Mark Clements Date: Sun, 24 Nov 2019 20:16:09 +0100 Subject: [PATCH] resolve in-process race condition --- index.js | 25 ++++++++++++++++++++++--- test.js | 2 +- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 7451574..d0fff4a 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,10 @@ 'use strict'; const net = require('net'); +const used = { + old: new Set(), + young: new Set() +}; const getAvailablePort = options => new Promise((resolve, reject) => { const server = net.createServer(); server.unref(); @@ -23,16 +27,31 @@ const portCheckSequence = function * (ports) { module.exports = async options => { let ports = null; - + const sweep = 1000 * 15; if (options) { ports = typeof options.port === 'number' ? [options.port] : options.port; } + const interval = setInterval(() => { + used.old = used.young; + used.young = new Set(); + }, sweep); + interval.unref(); for (const port of portCheckSequence(ports)) { try { - return await getAvailablePort({...options, port}); // eslint-disable-line no-await-in-loop + let p = await getAvailablePort({...options, port}); // eslint-disable-line no-await-in-loop + while (used.old.has(p) || used.young.has(p)) { + if (port !== 0) { + throw new Error('locked'); + } + + p = await getAvailablePort({...options, port}); // eslint-disable-line no-await-in-loop + } + + used.young.add(p); + return p; } catch (error) { - if (error.code !== 'EADDRINUSE') { + if (error.code !== 'EADDRINUSE' && error.message !== 'locked') { throw error; } } diff --git a/test.js b/test.js index 65f4ab7..15668df 100644 --- a/test.js +++ b/test.js @@ -44,7 +44,7 @@ test('port can be bound to IPv4 host when promise resolves', async t => { }); test('preferred port given IPv4 host', async t => { - const desiredPort = 8080; + const desiredPort = 8081; const port = await getPort({ port: desiredPort, host: '0.0.0.0'