From 7d34458553ca12c615af0de44a97e942c63854d9 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 3 May 2021 20:39:41 +0200 Subject: [PATCH] fixed race condition when using SSH tunnel #110 --- packages/api/src/utility/sshTunnel.js | 61 +++++++++++++++------------ 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/packages/api/src/utility/sshTunnel.js b/packages/api/src/utility/sshTunnel.js index d30eced35..25484526a 100644 --- a/packages/api/src/utility/sshTunnel.js +++ b/packages/api/src/utility/sshTunnel.js @@ -4,6 +4,8 @@ const portfinder = require('portfinder'); const stableStringify = require('json-stable-stringify'); const _ = require('lodash'); const platformInfo = require('./platformInfo'); +const AsyncLock = require('async-lock'); +const lock = new AsyncLock(); const sshConnectionCache = {}; const sshTunnelCache = {}; @@ -45,36 +47,43 @@ async function getSshConnection(connection) { } async function getSshTunnel(connection) { - const sshConn = await getSshConnection(connection); const tunnelCacheKey = stableStringify(_.pick(connection, TUNNEL_FIELDS)); - if (sshTunnelCache[tunnelCacheKey]) return sshTunnelCache[tunnelCacheKey]; - const localPort = await portfinder.getPortPromise({ port: 10000, stopPort: 60000 }); - // workaround for `getPortPromise` not releasing the port quickly enough - await new Promise(resolve => setTimeout(resolve, 500)); - const tunnelConfig = { - fromPort: localPort, - toPort: connection.port, - toHost: connection.server, - }; - try { - const tunnel = await sshConn.forward(tunnelConfig); - console.log( - `Created SSH tunnel to ${connection.sshHost}-${connection.server}:${connection.port}, using local port ${localPort}` - ); + return await lock.acquire(tunnelCacheKey, async () => { + const sshConn = await getSshConnection(connection); + if (sshTunnelCache[tunnelCacheKey]) return sshTunnelCache[tunnelCacheKey]; - sshTunnelCache[tunnelCacheKey] = { - state: 'ok', - localPort, + const localPort = await portfinder.getPortPromise({ port: 10000, stopPort: 60000 }); + // workaround for `getPortPromise` not releasing the port quickly enough + await new Promise(resolve => setTimeout(resolve, 500)); + const tunnelConfig = { + fromPort: localPort, + toPort: connection.port, + toHost: connection.server, }; - return sshTunnelCache[tunnelCacheKey]; - } catch (err) { - // error is not cached - return { - state: 'error', - message: err.message, - }; - } + try { + console.log( + `Creating SSH tunnel to ${connection.sshHost}-${connection.server}:${connection.port}, using local port ${localPort}` + ); + + const tunnel = await sshConn.forward(tunnelConfig); + console.log( + `Created SSH tunnel to ${connection.sshHost}-${connection.server}:${connection.port}, using local port ${localPort}` + ); + + sshTunnelCache[tunnelCacheKey] = { + state: 'ok', + localPort, + }; + return sshTunnelCache[tunnelCacheKey]; + } catch (err) { + // error is not cached + return { + state: 'error', + message: err.message, + }; + } + }); } module.exports = {