@@ -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
174214export async function stopDockerTunnel ( proxyPort : number | 'all' ) : Promise < void > {
0 commit comments