Skip to content

Commit cb79315

Browse files
committed
Cache Docker tunnel port for faster Docker tunneling
1 parent 9b4fb70 commit cb79315

File tree

1 file changed

+53
-13
lines changed

1 file changed

+53
-13
lines changed

src/interceptors/docker/docker-tunnel-proxy.ts

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ export async function ensureDockerTunnelRunning(proxyPort: number) {
9797
if (!container.State.Running) {
9898
await docker.getContainer(container.Id).start();
9999
}
100+
101+
// Asynchronously, update the Docker port that's in use for this container.
102+
portCache[proxyPort] = refreshDockerTunnelPortCache(proxyPort);
100103
});
101104
}
102105

@@ -153,22 +156,59 @@ export async function updateDockerTunnelledNetworks(
153156
});
154157
}
155158

156-
export async function getDockerTunnelPort(proxyPort: number): Promise<number> {
157-
const docker = new Docker();
159+
// A map of proxy port (e.g. 8000) to the automatically mapped Docker tunnel port.
160+
// Refreshed if it's somehow missing, or on every call to ensureDockerTunnelRunning(), e.g.
161+
// async at every docker-proxy request & every container interception.
162+
const portCache: { [proxyPort: string]: number | Promise<number> | undefined } = {};
158163

159-
const containerName = getDockerTunnelContainerName(proxyPort);
160-
let container = await docker.getContainer(containerName)
161-
.inspect().catch(() => undefined);
162-
if (!container) {
163-
// Can't get the container - recreate it first, then continue.
164-
await ensureDockerTunnelRunning(proxyPort);
165-
container = await docker.getContainer(containerName).inspect();
164+
export async function getDockerTunnelPort(proxyPort: number): Promise<number> {
165+
if (!portCache[proxyPort]) {
166+
// Update the port and wait for the query to complete:
167+
portCache[proxyPort] = refreshDockerTunnelPortCache(proxyPort);
166168
}
167169

168-
const portMappings = container.NetworkSettings.Ports['1080/tcp'];
169-
const localPort = _.find(portMappings, ({ HostIp }) => HostIp === '127.0.0.1');
170-
if (!localPort) throw new Error("No port mapped for Docker tunnel");
171-
return parseInt(localPort.HostPort, 10);
170+
return portCache[proxyPort]!;
171+
}
172+
173+
export async function refreshDockerTunnelPortCache(proxyPort: number): Promise<number> {
174+
try {
175+
if (_.isObject(portCache[proxyPort])) {
176+
// If there's an existing promise refreshing this data, then don't duplicate:
177+
return portCache[proxyPort]!
178+
}
179+
180+
const docker = new Docker();
181+
182+
const containerName = getDockerTunnelContainerName(proxyPort);
183+
let container = await docker.getContainer(containerName)
184+
.inspect().catch(() => undefined);
185+
if (!container) {
186+
// Can't get the container - recreate it (refreshing the port automatically)
187+
return ensureDockerTunnelRunning(proxyPort)
188+
.then(() => getDockerTunnelPort(proxyPort));
189+
}
190+
191+
const portMappings = container.NetworkSettings.Ports['1080/tcp'];
192+
const localPort = _.find(portMappings, ({ HostIp }) => HostIp === '127.0.0.1');
193+
194+
if (!localPort) {
195+
// This can happen if the networks of the container are changed manually. In some cases
196+
// this can result in the mapping being lots. Kill & restart the container.
197+
return docker.getContainer(containerName).kill()
198+
.then(() => ensureDockerTunnelRunning(proxyPort))
199+
.then(() => getDockerTunnelPort(proxyPort));
200+
}
201+
202+
const port = parseInt(localPort.HostPort, 10);
203+
204+
portCache[proxyPort] = port;
205+
return port;
206+
} catch (e) {
207+
// If something goes wrong, reset the port cache, to ensure that future checks
208+
// will query again from scratch:
209+
portCache[proxyPort] = undefined;
210+
throw e;
211+
}
172212
}
173213

174214
export async function stopDockerTunnel(proxyPort: number | 'all'): Promise<void> {

0 commit comments

Comments
 (0)