mongodb-memory-server-core
Version:
MongoDB Server for testing (core package, without autodownload). The server will allow you to connect your favourite ODM or client library to the MongoDB Server and run parallel integration tests isolated from each other.
125 lines • 4.82 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MAX_PORT = exports.MIN_PORT = void 0;
exports.getFreePort = getFreePort;
exports.validPort = validPort;
exports.tryPort = tryPort;
exports.resetPortsCache = resetPortsCache;
const tslib_1 = require("tslib");
const net = tslib_1.__importStar(require("node:net"));
const debug_1 = tslib_1.__importDefault(require("debug"));
const log = (0, debug_1.default)('MongoMS:GetPort');
/** Linux min port that does not require root permissions */
exports.MIN_PORT = 1024;
/** u16 max number */
exports.MAX_PORT = 65535;
/**
* Time before {@link PORTS_CACHE} gets cleared
* 10 seconds
*/
const PORTS_CACHE_CLEAN_TIME = 1000 * 10;
/**
* Ports cache, so that locked ports are quickly ignored and hoping for less port stealing
*/
const PORTS_CACHE = {
timeSet: undefined,
ports: new Set(),
};
/** 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 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
*/
async function getFreePort(firstPort, max_tries = MAX_DEFAULT_TRIES) {
// 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) {
PORTS_CACHE.ports.clear();
PORTS_CACHE.timeSet = Date.now();
}
else if (!PORTS_CACHE.timeSet) {
PORTS_CACHE.timeSet = Date.now();
}
let tries = 0;
while (tries <= max_tries) {
tries += 1;
// "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)"
if (PORTS_CACHE.ports.has(nextPort) && nextPort !== 0) {
continue;
}
PORTS_CACHE.ports.add(nextPort);
const triedPort = await tryPort(nextPort);
if (triedPort > 0) {
// check if triedPort is already in the cache (ie the vm executed another instance's getport before binary startup)
// and that the triedPort is not a custom port
const inCacheAndNotSame = PORTS_CACHE.ports.has(triedPort) && nextPort !== triedPort;
log(`getFreePort: found free port ${triedPort}, in cache and not custom: ${inCacheAndNotSame}`);
// returned port can be different than the "nextPort" (if net0listen)
PORTS_CACHE.ports.add(nextPort);
// ensure that no other instance can get the same port if the vm decides to run the other instance's getport before starting the last one
if (inCacheAndNotSame) {
continue;
}
// reset the cache time as we now have just added new ports
PORTS_CACHE.timeSet = Date.now();
return triedPort;
}
}
throw new Error('Max port tries exceeded');
}
exports.default = getFreePort;
/**
* 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
*/
function validPort(port) {
const mod = port % exports.MAX_PORT;
return mod < exports.MIN_PORT ? exports.MIN_PORT : mod;
}
/**
* Try a given port.
* @param port The port to try
* @returns the port if successful, "-1" in case of `EADDRINUSE`, all other errors reject
* @throws The error given if the code is not "EADDRINUSE"
*/
function tryPort(port) {
return new Promise((res, rej) => {
const server = net.createServer();
// some engines dont support ".unref"(net / tcp.unref), like "deno" in the past and now "bun"
if (typeof server.unref === 'function') {
server.unref(); // dont keep this server from exiting the application
}
server.on('error', (err) => {
if (err?.code !== 'EADDRINUSE') {
rej(err);
}
res(-1);
});
server.listen(port, () => {
const address = server.address();
const port = address.port;
server.close();
res(port);
});
});
}
/**
* Reset the {@link PORTS_CACHE} to its initial state.
*
* This function is meant for debugging and testing purposes only.
*/
function resetPortsCache() {
PORTS_CACHE.timeSet = undefined;
PORTS_CACHE.ports.clear();
}
//# sourceMappingURL=index.js.map