@@ -22,7 +22,6 @@ import {IpAddressTypes} from './ip-addresses';
2222import { AuthTypes } from './auth-types' ;
2323import { SQLAdminFetcher } from './sqladmin-fetcher' ;
2424import { CloudSQLConnectorError } from './errors' ;
25- import { isSameInstance , resolveInstanceName } from './parse-instance-connection-name' ;
2625
2726// These Socket types are subsets from nodejs definitely typed repo, ref:
2827// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/ae0fe42ff0e6e820e8ae324acf4f8e944aa1b2b7/types/node/v18/net.d.ts#L437
@@ -74,11 +73,24 @@ export declare interface TediousDriverOptions {
7473 connector : PromisedStreamFunction ;
7574 encrypt : boolean ;
7675}
76+ class CacheEntry {
77+ promise : Promise < CloudSQLInstance > ;
78+ instance ?: CloudSQLInstance ;
79+
80+ constructor ( promise : Promise < CloudSQLInstance > ) {
81+ this . promise = promise ;
82+ this . promise . then ( inst => ( this . instance = inst ) ) ;
83+ }
84+
85+ isResolved ( ) : boolean {
86+ return Boolean ( this . instance ) ;
87+ }
88+ }
7789
7890// Internal mapping of the CloudSQLInstances that
7991// adds extra logic to async initialize items.
80- class CloudSQLInstanceMap extends Map < string , CloudSQLInstance > {
81- private readonly sqlAdminFetcher : SQLAdminFetcher
92+ class CloudSQLInstanceMap extends Map < string , CacheEntry > {
93+ private readonly sqlAdminFetcher : SQLAdminFetcher ;
8294
8395 constructor ( sqlAdminFetcher : SQLAdminFetcher ) {
8496 super ( ) ;
@@ -91,53 +103,60 @@ class CloudSQLInstanceMap extends Map<string,CloudSQLInstance> {
91103 // https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector/pull/426
92104 // then the cache key should contain both the domain name
93105 // and the resolved instance name.
94- return ( opts . instanceConnectionName || opts . domainName ) + "-" +
95- opts . authType + "-" + opts . ipType ;
106+ return (
107+ ( opts . instanceConnectionName || opts . domainName ) +
108+ '-' +
109+ opts . authType +
110+ '-' +
111+ opts . ipType
112+ ) ;
96113 }
97114
98115 async loadInstance ( opts : ConnectionOptions ) : Promise < void > {
99116 // in case an instance to that connection name has already
100117 // been setup there's no need to set it up again
101- if ( this . has ( this . cacheKey ( opts ) ) ) {
102- const instance = this . get ( this . cacheKey ( opts ) ) ;
103- let oldInfo = instance ?. instanceInfo
104- if ( oldInfo && oldInfo . domainName ) {
105- // configured with domain name
106- let newInfo = await resolveInstanceName ( undefined , oldInfo . domainName ) ;
107- if ( ! isSameInstance ( oldInfo , newInfo ) ) {
108- // Domain name changed. Close and remove, then create a new map entry.
109- instance ?. close ( ) ;
110- this . delete ( this . cacheKey ( opts ) )
111- } else {
112- // Domain name resolves to the same instance, do nothing.
113- return
114- }
115- } else {
116- // Configured with instance name. Existing map entry is OK.
118+ const entry = this . get ( this . cacheKey ( opts ) ) ;
119+ if ( entry ) {
120+ if ( ! entry . isResolved ( ) ) {
121+ // A cache entry request is in progress.
122+ await entry . promise ;
117123 return ;
124+ } else {
125+ // Check the domain name, then if the instance is still open
126+ // return it
127+ await entry . instance ?. checkDomainChanged ( ) ;
128+ if ( ! entry . instance ?. isClosed ( ) ) {
129+ // The instance is open and the domain has not changed.
130+ // use the cached instance.
131+ return ;
132+ }
118133 }
119134 }
120135
121- const connectionInstance = await CloudSQLInstance . getCloudSQLInstance ( {
136+ // Start the refresh and add a cache entry immediately.
137+ const promise = CloudSQLInstance . getCloudSQLInstance ( {
122138 instanceConnectionName : opts . instanceConnectionName ,
123139 domainName : opts . domainName ,
124140 authType : opts . authType || AuthTypes . PASSWORD ,
125141 ipType : opts . ipType || IpAddressTypes . PUBLIC ,
126142 limitRateInterval : 30 * 1000 , // 30 sec
127143 sqlAdminFetcher : this . sqlAdminFetcher ,
128144 } ) ;
129- this . set ( this . cacheKey ( opts ) , connectionInstance ) ;
145+ this . set ( this . cacheKey ( opts ) , new CacheEntry ( promise ) ) ;
146+
147+ // Wait for the cache entry to resolve.
148+ await promise ;
130149 }
131150
132151 getInstance ( opts : ConnectionOptions ) : CloudSQLInstance {
133152 const connectionInstance = this . get ( this . cacheKey ( opts ) ) ;
134- if ( ! connectionInstance ) {
153+ if ( ! connectionInstance || ! connectionInstance . instance ) {
135154 throw new CloudSQLConnectorError ( {
136155 message : `Cannot find info for instance: ${ opts . instanceConnectionName } ` ,
137156 code : 'ENOINSTANCEINFO' ,
138157 } ) ;
139158 }
140- return connectionInstance ;
159+ return connectionInstance . instance ;
141160 }
142161}
143162
@@ -184,17 +203,17 @@ export class Connector {
184203 // const pool = new Pool(opts)
185204 // const res = await pool.query('SELECT * FROM pg_catalog.pg_tables;')
186205 async getOptions ( {
187- authType = AuthTypes . PASSWORD ,
188- ipType = IpAddressTypes . PUBLIC ,
189- instanceConnectionName,
190- domainName,
191- } : ConnectionOptions ) : Promise < DriverOptions > {
206+ authType = AuthTypes . PASSWORD ,
207+ ipType = IpAddressTypes . PUBLIC ,
208+ instanceConnectionName,
209+ domainName,
210+ } : ConnectionOptions ) : Promise < DriverOptions > {
192211 const { instances} = this ;
193212 await instances . loadInstance ( {
194213 ipType,
195214 authType,
196215 instanceConnectionName,
197- domainName
216+ domainName,
198217 } ) ;
199218
200219 return {
@@ -252,10 +271,10 @@ export class Connector {
252271 }
253272
254273 async getTediousOptions ( {
255- authType,
256- ipType,
257- instanceConnectionName,
258- } : ConnectionOptions ) : Promise < TediousDriverOptions > {
274+ authType,
275+ ipType,
276+ instanceConnectionName,
277+ } : ConnectionOptions ) : Promise < TediousDriverOptions > {
259278 if ( authType === AuthTypes . IAM ) {
260279 throw new CloudSQLConnectorError ( {
261280 message : 'Tedious does not support Auto IAM DB Authentication' ,
@@ -292,11 +311,11 @@ export class Connector {
292311 // `postgresql://${user}@localhost /${database}?host=${process.cwd()}`;
293312 // const prisma = new PrismaClient({ datasourceUrl });
294313 async startLocalProxy ( {
295- authType,
296- ipType,
297- instanceConnectionName,
298- listenOptions,
299- } : SocketConnectionOptions ) : Promise < void > {
314+ authType,
315+ ipType,
316+ instanceConnectionName,
317+ listenOptions,
318+ } : SocketConnectionOptions ) : Promise < void > {
300319 const { stream} = await this . getOptions ( {
301320 authType,
302321 ipType,
@@ -337,7 +356,7 @@ export class Connector {
337356 // Also clear up any local proxy servers and socket connections.
338357 close ( ) : void {
339358 for ( const instance of this . instances . values ( ) ) {
340- instance . close ( ) ;
359+ instance . promise . then ( inst => inst . close ( ) ) ;
341360 }
342361 for ( const server of this . localProxies ) {
343362 server . close ( ) ;
@@ -347,7 +366,7 @@ export class Connector {
347366 }
348367 }
349368
350- isClosed ( ) :boolean {
369+ isClosed ( ) : boolean {
351370 return this . closed ;
352371 }
353372}
0 commit comments